mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-07 17:14:22 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
459dfd7e35 | ||
|
|
d8d47977ac | ||
|
|
3daf28b6be | ||
|
|
f4b3e6e1c4 | ||
|
|
2b39684912 | ||
|
|
7e5e79d00f | ||
|
|
e551b4ae56 | ||
|
|
43ef1b494c | ||
|
|
be92aa1b4c | ||
|
|
07f3caadc4 | ||
|
|
3ad86ec5ad | ||
|
|
b5076d9262 | ||
|
|
77b4f06b07 | ||
|
|
4968499d11 | ||
|
|
f38991bc02 | ||
|
|
d0e6ee4b37 | ||
|
|
48ea09b27f | ||
|
|
9f22a2f202 | ||
|
|
7413a3da31 | ||
|
|
95aac6e367 | ||
|
|
9ed93a5281 | ||
|
|
3579fb1384 | ||
|
|
485bf0a0b7 | ||
|
|
b133b76b4e | ||
|
|
ffc46d277a | ||
|
|
dd3a5f6974 | ||
|
|
9a275b2db0 | ||
|
|
1c28222fe6 | ||
|
|
b93c5ace10 | ||
|
|
da9797c1c2 | ||
|
|
6dfe8b6f72 | ||
|
|
425221fb30 | ||
|
|
004a6dab88 | ||
|
|
6098f28bfa | ||
|
|
1205b1aaa3 | ||
|
|
ce6d2ca847 | ||
|
|
9e0ca3a415 | ||
|
|
b60b2e897f | ||
|
|
2c3e783f96 | ||
|
|
15bdd4a766 | ||
|
|
7c95e838f4 | ||
|
|
92aa64ce01 | ||
|
|
ae4bb08c3e | ||
|
|
6739525ebe | ||
|
|
d10b7aee4c | ||
|
|
aeb4c26bf0 | ||
|
|
ec6b8cd87c | ||
|
|
b2345f214e | ||
|
|
e9d9bd9ddb | ||
|
|
d7a7376eca | ||
|
|
9472ec1e93 | ||
|
|
e4b939dc1a | ||
|
|
c02190b5d4 | ||
|
|
e341e238ca | ||
|
|
7741a187ea | ||
|
|
4ef3c16d09 | ||
|
|
fb2ee19b90 | ||
|
|
74a300db6c | ||
|
|
57b9eb9107 | ||
|
|
b2d56fa37c | ||
|
|
a0ddbf5e16 | ||
|
|
1be8ad7770 | ||
|
|
24d217cfbd | ||
|
|
ec262d8d01 | ||
|
|
55ac6e11fc | ||
|
|
96d98cf192 | ||
|
|
6f0793eebc | ||
|
|
51cbdb465d | ||
|
|
61804b169b | ||
|
|
04e90bc754 | ||
|
|
8926a3a2bc | ||
|
|
bb7a85c8df | ||
|
|
3303ea949d | ||
|
|
cef48f4085 | ||
|
|
2ce27dab0d | ||
|
|
fa14663243 | ||
|
|
d2253110bd | ||
|
|
a3b22b748d | ||
|
|
584eccaa33 | ||
|
|
7dceeddca8 | ||
|
|
34da249779 | ||
|
|
c68c00c316 | ||
|
|
2e86d8afe2 | ||
|
|
f28d3ebb6c | ||
|
|
cfc021d88e | ||
|
|
9f92ed04fc | ||
|
|
b08f4b7bc8 | ||
|
|
d664bb49d4 | ||
|
|
c0d6e07f1c | ||
|
|
fa5a288a69 | ||
|
|
69ab2054e8 | ||
|
|
907e921043 | ||
|
|
a3f14df14d | ||
|
|
78dd777735 | ||
|
|
ee63072930 | ||
|
|
824d67d8f4 | ||
|
|
785ed9fdda | ||
|
|
5c1fb1d11b | ||
|
|
cdfd64f6de | ||
|
|
3727c93859 | ||
|
|
bc453b46c3 | ||
|
|
f47e417e4e | ||
|
|
7c79c73a5f | ||
|
|
3e9849f0ca | ||
|
|
149ebe0c47 | ||
|
|
d7817a7eac | ||
|
|
df620d7449 | ||
|
|
67678c2e1c | ||
|
|
734cb3bf82 | ||
|
|
adaa6abc08 | ||
|
|
dbb034a867 | ||
|
|
997e51c709 | ||
|
|
3e21808da9 | ||
|
|
405357c075 | ||
|
|
248f4510c6 | ||
|
|
de67c86432 | ||
|
|
101f9bc76b | ||
|
|
4879590a37 | ||
|
|
7194ced044 | ||
|
|
6a9deffbfb | ||
|
|
26e72f2b3e | ||
|
|
00663aa1bf | ||
|
|
18c8bfba3d | ||
|
|
4284c3fcdf | ||
|
|
a366e91038 | ||
|
|
736cf9d112 | ||
|
|
4dc8d60b9e | ||
|
|
d5fffb6769 | ||
|
|
51e8eb09ee | ||
|
|
e92a30bbf7 | ||
|
|
fcec11784a | ||
|
|
fe560c9835 | ||
|
|
27366fdeb8 | ||
|
|
5f79e1dc51 | ||
|
|
af6c8c8878 | ||
|
|
55390f1bf6 | ||
|
|
f2f55f3e1c | ||
|
|
db98e5edfb | ||
|
|
60072487f1 | ||
|
|
7ac5a34a59 | ||
|
|
eec3321ae9 | ||
|
|
d2a02a7b37 | ||
|
|
c1ce1897df | ||
|
|
5981c7290d | ||
|
|
cb540857cc | ||
|
|
8ba1bdf0b6 | ||
|
|
4ac98cd11a | ||
|
|
75cc3c3d5c | ||
|
|
a2acdf9bd9 | ||
|
|
14c0d73f89 | ||
|
|
3b4b261b5a | ||
|
|
000c31c155 | ||
|
|
197f00806e | ||
|
|
a06d378e8c | ||
|
|
e1770a3045 | ||
|
|
c983f1c232 | ||
|
|
f046fdd93e | ||
|
|
eb7e2c3568 | ||
|
|
4c0e08c2c8 | ||
|
|
2b7ec5e77a | ||
|
|
c17f835b3e | ||
|
|
1b47ae1c20 | ||
|
|
eb0043afc6 | ||
|
|
d9b3e17b66 | ||
|
|
2114692513 | ||
|
|
b5e4ff137a | ||
|
|
c049837c54 | ||
|
|
9e4b11adb7 | ||
|
|
47dfc57922 | ||
|
|
f5a119ffe5 | ||
|
|
545d8f748a | ||
|
|
a2b6771fe6 | ||
|
|
73f2a31d8b | ||
|
|
604b98c035 | ||
|
|
3fbd21a6e4 | ||
|
|
b2cba039d4 | ||
|
|
10d1c5a1ab | ||
|
|
954d54665f | ||
|
|
e78fb02b9e | ||
|
|
bf7abc054a | ||
|
|
27bc31e978 | ||
|
|
993f590ac2 | ||
|
|
e466f5c2a6 | ||
|
|
171d9dfb39 | ||
|
|
cb3fda4a7e | ||
|
|
4e11ee7d99 | ||
|
|
f6bee59b46 | ||
|
|
6dc1c2f72f | ||
|
|
14309dc111 | ||
|
|
b5253fa510 | ||
|
|
ea22cc8cef | ||
|
|
d427844465 | ||
|
|
7b78334ac4 | ||
|
|
5805f5e9d7 | ||
|
|
000ca1f637 | ||
|
|
0339b46450 | ||
|
|
ad6ef0e372 | ||
|
|
a814be2604 | ||
|
|
bc6228e8bc | ||
|
|
976f7cc175 | ||
|
|
15748d082d | ||
|
|
8b56039d88 | ||
|
|
5d32b24998 | ||
|
|
43aef76a37 | ||
|
|
d289e177fe | ||
|
|
c4a3d349d6 | ||
|
|
3bb756db45 | ||
|
|
cd0c34a6ed | ||
|
|
5409174aed | ||
|
|
c7649c95e2 | ||
|
|
8367da6500 | ||
|
|
b9cece5526 | ||
|
|
95fd121e1f | ||
|
|
8ec092522e | ||
|
|
8baa27e7a6 | ||
|
|
d094156606 | ||
|
|
5f5554fa1c | ||
|
|
0c6d5f5e71 | ||
|
|
fbce30675a | ||
|
|
6f4682a41a | ||
|
|
07ea0fa185 | ||
|
|
84b6c82ff3 | ||
|
|
b1e2b56445 | ||
|
|
b3d40438fb | ||
|
|
43071cd6e5 | ||
|
|
a8f4093695 | ||
|
|
f03582fb92 | ||
|
|
d10c70fb32 | ||
|
|
65f9d5761d | ||
|
|
4ef4746243 | ||
|
|
77957b4b41 | ||
|
|
bb1d16c3c6 | ||
|
|
e4d3c82501 | ||
|
|
2f2e0dc48c | ||
|
|
b53d476977 | ||
|
|
0bf914e9e2 | ||
|
|
1b21f7a043 | ||
|
|
2e09b7dd91 | ||
|
|
89650ace0d | ||
|
|
d643a33233 | ||
|
|
8d6dc0b121 | ||
|
|
a7c03851fe | ||
|
|
2f58fe7ae5 | ||
|
|
64d52b09f3 | ||
|
|
5ff96409fc | ||
|
|
8cf3ecdc5b | ||
|
|
0c1121d32c | ||
|
|
dafd42b2c3 | ||
|
|
3ff37a8b7e | ||
|
|
48fbffe449 | ||
|
|
e9f73bf440 | ||
|
|
045b8c2186 | ||
|
|
32840ad806 | ||
|
|
81d17c701c | ||
|
|
dd6b754453 | ||
|
|
57d810acb1 | ||
|
|
fd50d4c995 | ||
|
|
01e6970971 | ||
|
|
92cd96999f | ||
|
|
8b86cc3ba6 | ||
|
|
20f07795f0 | ||
|
|
d12d2ea78c | ||
|
|
8be72238b0 | ||
|
|
e608a46490 | ||
|
|
340b0bf0bc | ||
|
|
2e5a33cfea | ||
|
|
b8b29e1cc7 | ||
|
|
ff178aad6a | ||
|
|
8c8e49cc10 | ||
|
|
0b0f786002 | ||
|
|
9ba65eb414 | ||
|
|
5b783feabd | ||
|
|
5589e15051 | ||
|
|
40c1ef5890 | ||
|
|
e5af2bc3a3 | ||
|
|
6fa13de917 | ||
|
|
216e3edcde | ||
|
|
c334d46026 | ||
|
|
3e4e8b7055 | ||
|
|
0696c5cb4f | ||
|
|
ac855ab179 | ||
|
|
42c0798077 | ||
|
|
d69e294d54 | ||
|
|
94fc5d2769 | ||
|
|
fbe88c06db | ||
|
|
dd1edaa6c9 | ||
|
|
b23251d05b | ||
|
|
83e19216ab | ||
|
|
1399c7a969 | ||
|
|
8d104851ad | ||
|
|
9c83fa1099 | ||
|
|
f1d5afe328 | ||
|
|
05783e98c9 | ||
|
|
7547518e7c | ||
|
|
96a8db0243 | ||
|
|
5f15b53ee6 | ||
|
|
b73e74f1ff | ||
|
|
909d5833cd | ||
|
|
91be8af121 | ||
|
|
61bce7f6cf | ||
|
|
c195c14b13 | ||
|
|
e09112b7d4 | ||
|
|
d590a0b77f |
No files matched your search
-12
@@ -1,12 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
parallel = True
|
||||
concurrency = thread,multiprocessing
|
||||
omit =
|
||||
*/constants/*
|
||||
source =
|
||||
pwn
|
||||
pwnlib
|
||||
~/.cache/.pwntools-cache-2.7/
|
||||
~/.cache/.pwntools-cache-3.8/
|
||||
disable_warnings = module-not-imported
|
||||
@@ -39,7 +39,7 @@ You should see `[DEBUG]` statements that show what's happening behind the scenes
|
||||
|
||||
## Verify on Ubuntu
|
||||
|
||||
If possible, please verify that your issue occurs on 64-bit Ubuntu 18.04. We provide a Dockerfile based on Ubuntu 18.04 via `docker.io` to make this super simple, no VM required!
|
||||
If possible, please verify that your issue occurs on 64-bit Ubuntu 22.04. We provide a Dockerfile based on Ubuntu 22.04 via `docker.io` to make this super simple, no VM required!
|
||||
|
||||
```sh
|
||||
# Download the Docker image
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
interval: "weekly"
|
||||
@@ -6,24 +6,21 @@ jobs:
|
||||
android-test:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: ['3.10']
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: |
|
||||
**/pyproject.toml
|
||||
**/requirements*.txt
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: |
|
||||
@@ -35,8 +32,22 @@ jobs:
|
||||
binutils-arm-linux-gnueabihf \
|
||||
libc6-dbg
|
||||
|
||||
- name: Cache for avd
|
||||
uses: actions/cache@v4
|
||||
id: cache-avd
|
||||
with:
|
||||
path: |
|
||||
~/.android
|
||||
/usr/local/lib/android/sdk/emulator
|
||||
/usr/local/lib/android/sdk/platform-tools
|
||||
/usr/local/lib/android/sdk/system-images
|
||||
key: ${{ matrix.os }}-cache-avd-${{ hashFiles('travis/setup_avd*.sh') }}
|
||||
restore-keys: |
|
||||
${{ matrix.os }}-cache-avd-
|
||||
|
||||
- name: Install Android AVD
|
||||
run: |
|
||||
sudo usermod -aG kvm $USER
|
||||
source travis/setup_avd_fast.sh
|
||||
sed -i 's/skip_android = True/skip_android = False/' docs/source/conf.py
|
||||
set | grep ^PATH >.android.env
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
+144
-13
@@ -5,32 +5,86 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: [2.7, 3.8]
|
||||
python_version: ['3.10', '3.12']
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- python_version: '2.7'
|
||||
os: ubuntu-22.04
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
services:
|
||||
libcdb-cache:
|
||||
image: nginx
|
||||
volumes:
|
||||
- /home/runner/libcdb-cache:/var/cache/nginx
|
||||
ports:
|
||||
- 3000:3000 # https://debuginfod.elfutils.org proxy cache
|
||||
- 3001:3001 # https://libc.rip/ proxy cache
|
||||
- 3002:3002 # http://archive.ubuntu.com/ proxy cache
|
||||
- 3003:3003 # https://gitlab.com/ proxy cache
|
||||
- 3004:3004 # https://debuginfod.ubuntu.com/ proxy cache
|
||||
env:
|
||||
DEBUGINFOD_URLS: "http://localhost:3000/ http://localhost:3004/"
|
||||
PWN_LIBCRIP_URL: http://localhost:3001/
|
||||
PWN_UBUNTU_ARCHIVE_URL: http://localhost:3002/
|
||||
PWN_GITLAB_LIBCDB_URL: http://localhost:3003/
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-depth: 20
|
||||
|
||||
- name: Git History
|
||||
run: |
|
||||
git fetch origin
|
||||
git log --oneline --graph -10
|
||||
|
||||
- name: Fix libcdb-cache permissions
|
||||
id: fix-perms
|
||||
run: |
|
||||
sudo chown -R runner:runner /home/runner/libcdb-cache
|
||||
echo "date=$(/bin/date -u "+%Y%m%d%H%M%S")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache for libcdb requests
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/libcdb-cache
|
||||
key: libcdb-python${{ matrix.python_version }}-${{ steps.fix-perms.outputs.date }}
|
||||
restore-keys: |
|
||||
libcdb-python${{ matrix.python_version }}-
|
||||
libcdb-
|
||||
|
||||
- name: Install libcdb-cache service config
|
||||
run: |
|
||||
sudo chown -R 101:101 /home/runner/libcdb-cache
|
||||
container_id=$(docker ps --all --filter volume=/home/runner/libcdb-cache --no-trunc --format "{{.ID}}")
|
||||
docker cp ./travis/libcdb_nginx_cache.conf $container_id:/etc/nginx/nginx.conf
|
||||
docker restart $container_id
|
||||
|
||||
- name: Install RPyC for gdb
|
||||
run: |
|
||||
# The version packaged in python3-rpyc is too old on Ubuntu 24.04
|
||||
# We use ^6.0 from pip.
|
||||
sudo apt-get update && sudo apt-get install -y python3-pip gdb gdbserver
|
||||
/usr/bin/python -m pip install --break-system-packages rpyc || /usr/bin/python -m pip install rpyc
|
||||
gdb --batch --quiet --nx --nh --ex 'py import rpyc; print(rpyc.version.version)'
|
||||
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip
|
||||
uses: actions/cache@v4
|
||||
if: matrix.python_version == '2.7'
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
key: ${{ matrix.os }}-${{ matrix.python_version }}-cache-pip-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }}
|
||||
restore-keys: ${{ matrix.os }}-${{ matrix.python_version }}-cache-pip-
|
||||
|
||||
- name: Set up Python ${{ matrix.python_version }}
|
||||
if: matrix.python_version != '2.7'
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: |
|
||||
**/pyproject.toml
|
||||
**/requirements*.txt
|
||||
|
||||
- name: Set up Python 2.7
|
||||
if: matrix.python_version == '2.7'
|
||||
@@ -62,19 +116,21 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends -o Acquire::Retries=3 \
|
||||
ash bash-static dash ksh mksh zsh \
|
||||
python3-rpyc \
|
||||
gdb gdbserver socat \
|
||||
binutils-multiarch qemu-user-static \
|
||||
binutils-aarch64-linux-gnu \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
binutils-mips-linux-gnu \
|
||||
binutils-mipsel-linux-gnu \
|
||||
binutils-msp430 \
|
||||
binutils-powerpc-linux-gnu \
|
||||
binutils-s390x-linux-gnu \
|
||||
binutils-sparc64-linux-gnu \
|
||||
binutils-riscv64-linux-gnu \
|
||||
gcc-multilib \
|
||||
libc6-dbg \
|
||||
elfutils
|
||||
elfutils \
|
||||
patchelf
|
||||
|
||||
- name: Testing Corefiles
|
||||
run: |
|
||||
@@ -94,6 +150,8 @@ jobs:
|
||||
pip install --upgrade wheel build
|
||||
pip install --upgrade flake8 appdirs
|
||||
pip install --upgrade --editable .
|
||||
# Pin to known working version until 2.1.5 is out. https://github.com/unicorn-engine/unicorn/issues/2134
|
||||
pip install 'unicorn==2.1.2'
|
||||
|
||||
- name: Sanity checks
|
||||
run: PWNLIB_NOTERM=1 python -bb -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
@@ -111,6 +169,10 @@ jobs:
|
||||
|
||||
- name: Coverage doctests
|
||||
run: |
|
||||
# Python version installed using setup-python interferes with gdb's python
|
||||
# by setting LD_LIBRARY_PATH and gdb's python becoming unable to load built-in modules
|
||||
# like _socket. This is a workaround.
|
||||
unset LD_LIBRARY_PATH
|
||||
PWNLIB_NOTERM=1 python -bb -m coverage run -m sphinx -b doctest docs/source docs/build/doctest
|
||||
|
||||
- name: Coverage running examples
|
||||
@@ -138,11 +200,18 @@ jobs:
|
||||
pwn shellcraft --list |tail
|
||||
pwn shellcraft -l --syscalls |tail
|
||||
pwn shellcraft -l execve
|
||||
pwn shellcraft -l execve + exit
|
||||
pwn shellcraft --show i386.linux.loader_append
|
||||
pwn shellcraft --show i386.linux.loader_append + i386.linux.sh
|
||||
pwn shellcraft -f asm --color amd64.linux.sh
|
||||
pwn shellcraft -f asm --color amd64.linux.setreuid + amd64.linux.cat /etc/passwd
|
||||
pwn shellcraft -f asm --color amd64.linux.setreuid = amd64.linux.cat /key+secret --delim =
|
||||
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
|
||||
pwn shellcraft -f elf amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null |pwn hex
|
||||
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
|
||||
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd + amd64.linux.sh </dev/null
|
||||
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
|
||||
pwn shellcraft -f c amd64.linux.cat /etc/passwd + 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
|
||||
@@ -151,6 +220,11 @@ jobs:
|
||||
pwn disasm --color ff3424c3ebfe
|
||||
pwn asm -f hex nop
|
||||
|
||||
pwn hex ABCD
|
||||
pwn hex ABCD --separator ' '
|
||||
pwn hex ABCD --prefix '\x'
|
||||
pwn hex ABCD -p '0x' -s ' '
|
||||
|
||||
pwn hex abcd
|
||||
pwn unhex 4141 4141
|
||||
|
||||
@@ -158,6 +232,7 @@ jobs:
|
||||
pwn phd -l 0x3d --color=always /etc/os-release
|
||||
|
||||
pwn checksec /bin/bash
|
||||
(ulimit -v 500000 && pwn checksec /bin/bash)
|
||||
|
||||
pwn errno 2
|
||||
pwn errno -1
|
||||
@@ -166,32 +241,87 @@ jobs:
|
||||
pwn constgrep -c freebsd -m ^PROT_ '3 + 4'
|
||||
pwn constgrep ^MAP_ 0
|
||||
pwn constgrep -e O_RDWR
|
||||
pwn constgrep C
|
||||
|
||||
pwn libcdb file /lib/x86_64-linux-gnu/libc.so.6
|
||||
pwn libcdb lookup puts 5f0 __libc_start_main_ret d0a
|
||||
pwn libcdb hash b229d1da1e161f95e839cf90cded5f719e5de308
|
||||
|
||||
- name: Build source and wheel distributions
|
||||
if: matrix.python_version > '2.7'
|
||||
if: matrix.python_version != '2.7'
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.python_version == '3.10'
|
||||
with:
|
||||
name: packages
|
||||
path: dist/
|
||||
include-hidden-files: true
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-${{ matrix.python_version }}
|
||||
path: .coverage*
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Fix libcdb-cache permissions
|
||||
run: |
|
||||
container_id=$(docker ps --filter volume=/home/runner/libcdb-cache --no-trunc --format "{{.ID}}")
|
||||
docker stop $container_id
|
||||
sudo chown -R runner:runner /home/runner/libcdb-cache
|
||||
|
||||
windows-test:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade --editable .
|
||||
|
||||
- name: Sanity checks
|
||||
run: |
|
||||
python -bb -c 'from pwn import *'
|
||||
python -bb examples/text.py
|
||||
|
||||
upload-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 20
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: coverage-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Install coveralls
|
||||
run: |
|
||||
pip install --break-system-packages tomli coveralls || pip install tomli coveralls
|
||||
|
||||
- name: Upload coverage to coveralls.io
|
||||
run: |
|
||||
coverage combine
|
||||
COVERALLS_REPO_TOKEN=PP20MEgztXIQJJTguQwe2jeCh6Bm4lkbv coveralls
|
||||
|
||||
|
||||
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: test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 20
|
||||
- name: Push changes to protected branch
|
||||
@@ -204,6 +334,7 @@ jobs:
|
||||
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')
|
||||
@@ -212,7 +343,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: packages
|
||||
path: dist
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Publish Docker images
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- beta
|
||||
- stable
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'Gallopsled'
|
||||
steps:
|
||||
# Required for subdirectories in Git context
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Build and push base image
|
||||
uses: docker/build-push-action@v6
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
context: "{{defaultContext}}:extra/docker/base"
|
||||
push: true
|
||||
tags: pwntools/pwntools:base
|
||||
|
||||
- name: Build and push stable image
|
||||
uses: docker/build-push-action@v6
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/stable')
|
||||
with:
|
||||
context: "{{defaultContext}}:extra/docker/stable"
|
||||
push: true
|
||||
tags: pwntools/pwntools:stable
|
||||
|
||||
- name: Build and push beta image
|
||||
uses: docker/build-push-action@v6
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/beta')
|
||||
with:
|
||||
context: "{{defaultContext}}:extra/docker/beta"
|
||||
push: true
|
||||
tags: pwntools/pwntools:beta
|
||||
|
||||
- name: Build and push dev image
|
||||
uses: docker/build-push-action@v6
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/dev')
|
||||
with:
|
||||
context: "{{defaultContext}}:extra/docker/dev"
|
||||
push: true
|
||||
tags: |
|
||||
pwntools/pwntools:dev
|
||||
pwntools/pwntools:latest
|
||||
|
||||
- name: Build and push ci image
|
||||
uses: docker/build-push-action@v6
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/dev')
|
||||
with:
|
||||
context: "{{defaultContext}}:travis/docker"
|
||||
push: true
|
||||
tags: pwntools/pwntools:ci
|
||||
@@ -5,23 +5,21 @@ jobs:
|
||||
lint:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: ['3.10']
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: |
|
||||
**/pyproject.toml
|
||||
**/requirements*.txt
|
||||
|
||||
- name: Critical lint
|
||||
run: |
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -5,33 +5,32 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: ['3.10']
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: |
|
||||
**/pyproject.toml
|
||||
**/requirements*.txt
|
||||
|
||||
- name: PyLint
|
||||
run: |
|
||||
set -x
|
||||
pip install pylint
|
||||
pip install 'pylint<4'
|
||||
pip install --upgrade -e .
|
||||
pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- > current.txt
|
||||
run_pylint() { pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- | sed 's/line [0-9]\+/line XXXX/g'; }
|
||||
run_pylint > current.txt
|
||||
git fetch origin
|
||||
git checkout origin/"$GITHUB_BASE_REF"
|
||||
pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- > base.txt
|
||||
run_pylint > base.txt
|
||||
if diff base.txt current.txt | grep '>'; then
|
||||
false
|
||||
fi
|
||||
+4
-1
@@ -5,7 +5,10 @@ version: 2
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3"
|
||||
python: "3.12"
|
||||
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
python:
|
||||
install:
|
||||
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
language: python
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lib32stdc++6
|
||||
- ash
|
||||
- bash
|
||||
- dash
|
||||
- ksh
|
||||
- mksh
|
||||
- zsh
|
||||
- pandoc
|
||||
- gdb
|
||||
- socat
|
||||
- sshpass
|
||||
- binutils
|
||||
- qemu-user-static
|
||||
- binutils-multiarch
|
||||
- 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
|
||||
- bash-static
|
||||
- python3-pip
|
||||
- elfutils
|
||||
cache:
|
||||
- pip
|
||||
- directories:
|
||||
- usr
|
||||
- /home/travis/virtualenv/python2.7.15/lib/python2.7/site-packages/
|
||||
- /home/travis/virtualenv/python2.7.15/bin/
|
||||
- /home/travis/virtualenv/python3.8.*/lib/python3.8/site-packages/
|
||||
- /home/travis/virtualenv/python3.8.*/bin/
|
||||
- docs/build/doctest/.doctrees
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.8"
|
||||
before_install:
|
||||
- echo $-
|
||||
- source travis/install.sh
|
||||
- source travis/ssh_setup.sh
|
||||
- echo $-
|
||||
install:
|
||||
- /usr/bin/python3 -m pip install rpyc # for GDB
|
||||
- pip install --upgrade pip
|
||||
- pip install --upgrade flake8 appdirs # https://github.com/ActiveState/appdirs/issues/89#issuecomment-282326570
|
||||
- python setup.py egg_info # pypa/pip#6275
|
||||
- pip install --upgrade --editable .
|
||||
- pip install --upgrade --requirement docs/requirements.txt
|
||||
before_script:
|
||||
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
- sudo sh -c "echo 0 > /proc/sys/kernel/yama/ptrace_scope"
|
||||
script:
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
- flake8 . --count --select=E9,F63,F7 --show-source --statistics --exclude=android-?dk # TODO: Add F82
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
- PWNLIB_NOTERM=1 coverage run -m sphinx -b doctest docs/source docs/build/doctest
|
||||
- coverage combine
|
||||
after_success:
|
||||
- coveralls
|
||||
- source travis/update_demo.sh
|
||||
+305
-8
@@ -9,9 +9,15 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
|
||||
| Version | Branch | Release Date |
|
||||
| ---------------- | -------- | ---------------------- |
|
||||
| [4.13.0](#4130-dev) | `dev` |
|
||||
| [4.12.0](#4120-beta) | `beta` |
|
||||
| [4.11.0](#4110-stable) | `stable` | Sep 15, 2023
|
||||
| [5.0.0](#500-dev) | `dev` |
|
||||
| [4.15.0](#4150-stable) | `stable` | Oct 12, 2025
|
||||
| [4.14.1](#4141) | | Mar 24, 2025
|
||||
| [4.14.0](#4140) | | Jan 15, 2025
|
||||
| [4.13.1](#4131) | | Sep 29, 2024
|
||||
| [4.13.0](#4130) | | Aug 12, 2024
|
||||
| [4.12.0](#4120) | | Feb 22, 2024
|
||||
| [4.11.1](#4111) | | Nov 14, 2023
|
||||
| [4.11.0](#4110) | | Sep 15, 2023
|
||||
| [4.10.0](#4100) | | May 21, 2023
|
||||
| [4.9.0](#490) | | Dec 29, 2022
|
||||
| [4.8.0](#480) | | Apr 21, 2022
|
||||
@@ -67,11 +73,284 @@ 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.13.0 (`dev`)
|
||||
## 5.0.0 (`dev`)
|
||||
|
||||
- [#2598][2598] aarch64: Fix ABI definition
|
||||
- [#2419][2419] riscv: avoid compressed instructions (if you need compressed, use .option rvc)
|
||||
- [#2551][2551] Detect when kitty is being used as terminal
|
||||
- [#2519][2519] Drop Python 2.7 support / Require Python 3.10
|
||||
- [#2507][2507] Add `+LINUX` and `+WINDOWS` doctest options and start proper testing on Windows
|
||||
- [#2522][2522] Support starting a kitty debugging window with the 'kitten' command
|
||||
- [#2524][2524] Raise EOFError during `process.recv` when stdout closes on Windows
|
||||
- [#2526][2526] Properly make use of extra arguments in `packing` utilities. `sign` parameter requires keyword syntax to specify it.
|
||||
- [#2517][2517] Allow to passthru kwargs on `ssh.__getattr__` convenience function to fix SSH motd problems
|
||||
- [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool.
|
||||
- [#2529][2529] Add LoongArch64 support
|
||||
- [#2506][2506] ROP: fix `ROP(ELF(exe)).leave` is `None` in some ELF
|
||||
- [#2504][2504] doc: add example case for `tuple` (host, port pair) in `gdb.attach`
|
||||
- [#2546][2546] ssh: Allow passing disabled_algorithms keyword argument from ssh to paramiko
|
||||
- [#2538][2538] Add `ssh -L` / `ssh.connect_remote()` workaround when `AllowTcpForwarding` is disabled
|
||||
- [#2574][2574] Allow creating an ELF from in-memory bytes
|
||||
- [#2575][2575] Detect when Terminator is being used as terminal
|
||||
- [#2578][2578] Add gnome-terminal, Alacritty, Ttilix for run_in_new_terminal
|
||||
- [#2590][2590] Add support for finding corefiles under WSL2
|
||||
- [#2496][2496] Add linux ko file search support
|
||||
- [#2542][2542] Decode `_IO_*` flags in `FileStructure` member
|
||||
- [#2592][2592] pwnlib.config: Fix customization of `context.timeout`
|
||||
- [#2608][2608] Abort on `libcdb file libc.so --unstrip` if eu-unstrip is not installed
|
||||
- [#2611][2611] Cleanup `pwnlib.lexer` exports and imports
|
||||
- [#2610][2610] Fix `log.progress` ignoring `context.log_console`
|
||||
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
|
||||
|
||||
[2598]: https://github.com/Gallopsled/pwntools/pull/2598
|
||||
[2419]: https://github.com/Gallopsled/pwntools/pull/2419
|
||||
[2551]: https://github.com/Gallopsled/pwntools/pull/2551
|
||||
[2519]: https://github.com/Gallopsled/pwntools/pull/2519
|
||||
[2507]: https://github.com/Gallopsled/pwntools/pull/2507
|
||||
[2522]: https://github.com/Gallopsled/pwntools/pull/2522
|
||||
[2524]: https://github.com/Gallopsled/pwntools/pull/2524
|
||||
[2526]: https://github.com/Gallopsled/pwntools/pull/2526
|
||||
[2517]: https://github.com/Gallopsled/pwntools/pull/2517
|
||||
[2530]: https://github.com/Gallopsled/pwntools/pull/2530
|
||||
[2529]: https://github.com/Gallopsled/pwntools/pull/2529
|
||||
[2506]: https://github.com/Gallopsled/pwntools/pull/2506
|
||||
[2504]: https://github.com/Gallopsled/pwntools/pull/2504
|
||||
[2546]: https://github.com/Gallopsled/pwntools/pull/2546
|
||||
[2538]: https://github.com/Gallopsled/pwntools/pull/2538
|
||||
[2574]: https://github.com/Gallopsled/pwntools/pull/2574
|
||||
[2575]: https://github.com/Gallopsled/pwntools/pull/2575
|
||||
[2578]: https://github.com/Gallopsled/pwntools/pull/2578
|
||||
[2590]: https://github.com/Gallopsled/pwntools/pull/2590
|
||||
[2496]: https://github.com/Gallopsled/pwntools/pull/2496
|
||||
[2542]: https://github.com/Gallopsled/pwntools/pull/2542
|
||||
[2592]: https://github.com/Gallopsled/pwntools/pull/2592
|
||||
[2608]: https://github.com/Gallopsled/pwntools/pull/2608
|
||||
[2611]: https://github.com/Gallopsled/pwntools/pull/2611
|
||||
[2610]: https://github.com/Gallopsled/pwntools/pull/2610
|
||||
[2615]: https://github.com/Gallopsled/pwntools/pull/2615
|
||||
|
||||
## 4.15.1
|
||||
|
||||
- [#2694][2694] fix: pad bytes fields to correct field size in FileStructure
|
||||
- [#2701][2701] Fix `adb._build_date()` crash on devices with non-standard locale date strings
|
||||
- [#2721][2721] Allow running with Unicorn 2.1.[34] but throw when emulating MIPS
|
||||
- [#2773][2773] Disable debuginfod doctests in libcdb.unstrip_libc
|
||||
- [#2772][2772] fix(packing): `dd()` no longer crashes on a `bytearray` destination
|
||||
- [#2787][2787] gdb.corefile: Run gdb directly to prevent detach race
|
||||
- [#2795][2795] regsort: Fix self-assignment detection through register aliases
|
||||
|
||||
[2694]: https://github.com/Gallopsled/pwntools/pull/2694
|
||||
[2701]: https://github.com/Gallopsled/pwntools/pull/2701
|
||||
[2721]: https://github.com/Gallopsled/pwntools/pull/2721
|
||||
[2773]: https://github.com/Gallopsled/pwntools/pull/2773
|
||||
[2772]: https://github.com/Gallopsled/pwntools/pull/2772
|
||||
[2787]: https://github.com/Gallopsled/pwntools/pull/2787
|
||||
[2795]: https://github.com/Gallopsled/pwntools/pull/2795
|
||||
|
||||
## 4.15.0 (`stable`)
|
||||
|
||||
- [#2508][2508] Ignore a warning when compiling with asm on nix
|
||||
- [#2471][2471] Properly close spawned kitty window
|
||||
- [#2358][2358] Cache output of `asm()`
|
||||
- [#2457][2457] Catch exception of non-ELF files in checksec.
|
||||
- [#2444][2444] Add `ELF.close()` to release resources
|
||||
- [#2413][2413] libcdb: improve the search speed of `search_by_symbol_offsets` in local libc-database
|
||||
- [#2470][2470] Fix waiting for gdb under WSL2
|
||||
- [#2479][2479] Support extracting libraries from Docker image in `pwn template`
|
||||
- [#2483][2483] Only print `checksec` output of `ELF.libc` when it was printed for the `ELF` already
|
||||
- [#2482][2482] Throw error when using `sni` and setting `server_hostname` manually in `remote`
|
||||
- [#2478][2478] libcdb-cli: add `--offline-only`, refactor unstrip and add fetch parser for download libc-database
|
||||
- [#2484][2484] Allow to disable caching
|
||||
- [#2291][2291] Fix attaching to a gdbserver with tuple `gdb.attach(('0.0.0.0',12345))`
|
||||
- [#2410][2410] Add `tube.upload_manually` to upload files in chunks
|
||||
- [#2502][2502] Fix loading ELF files without valid .dynamic section
|
||||
- [#2476][2476] Deprecate 'keepends' argument in favor of 'drop' in `tube.recvline*`
|
||||
- [#2364][2364] Deprecate direct commandline scripts invocation and exclude nonsense ones
|
||||
- [#2570][2570] Fix `pwn template` to skip Docker library extraction for statically linked binaries
|
||||
- [#2545][2545] SSH: fix download/upload with -1 exit status
|
||||
- [#2567][2567] Fix mistakenly parsing of ld-linux error messages.
|
||||
- [#2576][2576] regsort: respect register aliases
|
||||
- [#2595][2595] libcdb: Add Ubuntu and Debian debuginfod servers to default list
|
||||
- [#2593][2593] Use unicorn on macOS w/ SIP enabled
|
||||
- [#2587][2587] Support longer function names in Windows `getexport` shellcode
|
||||
- [#2596][2596] Ignore `colored_traceback` error when TERM envvar is unset
|
||||
- [#2579][2579] Fix poll error in `process.libs()` and clean up maps parsing
|
||||
- [#2602][2602] Allow setting debugger path via context.gdb_binary
|
||||
- [#2609][2609] Fix log level of child remotes of `server` tube
|
||||
- [#2612][2612] Fix lookup of binutils for `mipsel` architecture
|
||||
- [#2624][2624] Fix regression: gdbserver can't handle command-line argument containing whitespace
|
||||
|
||||
[2508]: https://github.com/Gallopsled/pwntools/pull/2508
|
||||
[2471]: https://github.com/Gallopsled/pwntools/pull/2471
|
||||
[2358]: https://github.com/Gallopsled/pwntools/pull/2358
|
||||
[2457]: https://github.com/Gallopsled/pwntools/pull/2457
|
||||
[2444]: https://github.com/Gallopsled/pwntools/pull/2444
|
||||
[2413]: https://github.com/Gallopsled/pwntools/pull/2413
|
||||
[2470]: https://github.com/Gallopsled/pwntools/pull/2470
|
||||
[2479]: https://github.com/Gallopsled/pwntools/pull/2479
|
||||
[2483]: https://github.com/Gallopsled/pwntools/pull/2483
|
||||
[2482]: https://github.com/Gallopsled/pwntools/pull/2482
|
||||
[2478]: https://github.com/Gallopsled/pwntools/pull/2478
|
||||
[2484]: https://github.com/Gallopsled/pwntools/pull/2484
|
||||
[2291]: https://github.com/Gallopsled/pwntools/pull/2291
|
||||
[2410]: https://github.com/Gallopsled/pwntools/pull/2410
|
||||
[2502]: https://github.com/Gallopsled/pwntools/pull/2502
|
||||
[2476]: https://github.com/Gallopsled/pwntools/pull/2476
|
||||
[2364]: https://github.com/Gallopsled/pwntools/pull/2364
|
||||
[2570]: https://github.com/Gallopsled/pwntools/pull/2570
|
||||
[2545]: https://github.com/Gallopsled/pwntools/pull/2545
|
||||
[2567]: https://github.com/Gallopsled/pwntools/pull/2567
|
||||
[2576]: https://github.com/Gallopsled/pwntools/pull/2576
|
||||
[2595]: https://github.com/Gallopsled/pwntools/pull/2595
|
||||
[2593]: https://github.com/Gallopsled/pwntools/pull/2593
|
||||
[2587]: https://github.com/Gallopsled/pwntools/pull/2587
|
||||
[2596]: https://github.com/Gallopsled/pwntools/pull/2596
|
||||
[2579]: https://github.com/Gallopsled/pwntools/pull/2579
|
||||
[2602]: https://github.com/Gallopsled/pwntools/pull/2602
|
||||
[2609]: https://github.com/Gallopsled/pwntools/pull/2609
|
||||
[2612]: https://github.com/Gallopsled/pwntools/pull/2612
|
||||
[2624]: https://github.com/Gallopsled/pwntools/pull/2624
|
||||
|
||||
## 4.14.1
|
||||
|
||||
- [#2451][2451] Show symbols defined to value 0 (start of file)
|
||||
- [#2533][2533] Fix installation on Python 3.5 and lower
|
||||
- [#2518][2518] fix: update apport coredump path handling for CorefileFinder
|
||||
- [#2552][2552] Fix memcpy shellcraft template off-by-one
|
||||
- [#2559][2559] Fix parsing corefile with missing auxv
|
||||
- [#2562][2562] Fix syntax highlighting of multiline comments in ASM
|
||||
- [#2565][2565] Exclude broken Unicorn
|
||||
|
||||
[2451]: https://github.com/Gallopsled/pwntools/pull/2451
|
||||
[2533]: https://github.com/Gallopsled/pwntools/pull/2533
|
||||
[2518]: https://github.com/Gallopsled/pwntools/pull/2518
|
||||
[2552]: https://github.com/Gallopsled/pwntools/pull/2552
|
||||
[2559]: https://github.com/Gallopsled/pwntools/pull/2559
|
||||
[2562]: https://github.com/Gallopsled/pwntools/pull/2562
|
||||
[2565]: https://github.com/Gallopsled/pwntools/pull/2565
|
||||
|
||||
## 4.14.0
|
||||
|
||||
- [#2356][2356] Add local libc database provider for libcdb
|
||||
- [#2360][2360] Add offline parameter for `search_by_hash` series function
|
||||
- [#2388][2388] libcdb: add `offline_only` to `search_by_symbol_offsets`
|
||||
- [#2374][2374] libcdb.unstrip_libc: debug symbols are fetched only if not present
|
||||
- [#2327][2327] Add basic support to debug processes on Windows
|
||||
- [#2437][2437] Support asm/disasm on Windows
|
||||
- [#2330][2330] Change `context.newline` when setting `context.os` to `"windows"`
|
||||
- [#2322][2322] Add basic RISCV64 shellcraft support
|
||||
- [#2376][2376] Return buffered data on first EOF in tube.readline()
|
||||
- [#2371][2371] Add functions for retrieving process mappings
|
||||
- [#2398][2398] Add support for generating multiple shellcodes at a time in shellcraft
|
||||
- [#2389][2389] Fix passing bytes to `context.log_file` and `crc.BitPolynom`
|
||||
- [#2391][2391] Fix error message when passing invalid kwargs to `xor`
|
||||
- [#2387][2387] Convert apport_corefile() output from bytes-like object to string
|
||||
- [#2415][2415] Add shellcraft template for IPv6 socket
|
||||
- [#2405][2405] Add "none" ssh authentication method
|
||||
- [#2427][2427] Document behaviour of remote()'s sni argument as string.
|
||||
- [#2382][2382] added optional port, gdb_args and gdbserver_args parameters to gdb.debug()
|
||||
- [#2435][2435] Speed up gdbserver handshake in gdb.debug()
|
||||
- [#2436][2436] Add resolution_addr parameter to Ret2dlresolvePayload
|
||||
- [#2497][2497] Fix remote.fromsocket() to handle AF_INET6 socket
|
||||
|
||||
[2436]: https://github.com/Gallopsled/pwntools/pull/2436
|
||||
[2371]: https://github.com/Gallopsled/pwntools/pull/2371
|
||||
[2360]: https://github.com/Gallopsled/pwntools/pull/2360
|
||||
[2356]: https://github.com/Gallopsled/pwntools/pull/2356
|
||||
[2374]: https://github.com/Gallopsled/pwntools/pull/2374
|
||||
[2327]: https://github.com/Gallopsled/pwntools/pull/2327
|
||||
[2322]: https://github.com/Gallopsled/pwntools/pull/2322
|
||||
[2330]: https://github.com/Gallopsled/pwntools/pull/2330
|
||||
[2389]: https://github.com/Gallopsled/pwntools/pull/2389
|
||||
[2391]: https://github.com/Gallopsled/pwntools/pull/2391
|
||||
[2376]: https://github.com/Gallopsled/pwntools/pull/2376
|
||||
[2387]: https://github.com/Gallopsled/pwntools/pull/2387
|
||||
[2388]: https://github.com/Gallopsled/pwntools/pull/2388
|
||||
[2398]: https://github.com/Gallopsled/pwntools/pull/2398
|
||||
[2415]: https://github.com/Gallopsled/pwntools/pull/2415
|
||||
[2405]: https://github.com/Gallopsled/pwntools/pull/2405
|
||||
[2427]: https://github.com/Gallopsled/pwntools/pull/2405
|
||||
[2382]: https://github.com/Gallopsled/pwntools/pull/2382
|
||||
[2435]: https://github.com/Gallopsled/pwntools/pull/2435
|
||||
[2437]: https://github.com/Gallopsled/pwntools/pull/2437
|
||||
[2497]: https://github.com/Gallopsled/pwntools/pull/2497
|
||||
|
||||
## 4.13.1
|
||||
|
||||
- [#2445][2445] Fix parsing the PLT on Windows
|
||||
- [#2466][2466] Fix PLT emulation with Unicorn 2.1.0
|
||||
- [#2466][2466] Switch to PyPi Simple API for update checks
|
||||
- [#2467][2467] Fix loading at all on Windows
|
||||
- [#2469][2469] GDB 15+ rpyc detection
|
||||
|
||||
[2445]: https://github.com/Gallopsled/pwntools/pull/2445
|
||||
[2466]: https://github.com/Gallopsled/pwntools/pull/2466
|
||||
[2467]: https://github.com/Gallopsled/pwntools/pull/2467
|
||||
[2469]: https://github.com/Gallopsled/pwntools/pull/2469
|
||||
|
||||
## 4.13.0
|
||||
|
||||
- [#2242][2242] Term module revamp: activating special handling of terminal only when necessary
|
||||
- [#2277][2277] elf: Resolve more relocations into GOT entries
|
||||
- [#2281][2281] FIX: Getting right amount of data for search fix
|
||||
- [#2293][2293] Add x86 CET status to checksec output
|
||||
- [#1763][1763] Allow to add to the existing environment in `process` instead of replacing it
|
||||
- [#2307][2307] Fix `pwn libcdb file` crashing if "/bin/sh" string was not found
|
||||
- [#2309][2309] Detect challenge binary and libc in `pwn template`
|
||||
- [#2308][2308] Fix WinExec shellcraft to make sure it's 16 byte aligned
|
||||
- [#2279][2279] Make `pwn template` always set context.binary
|
||||
- [#2310][2310] Add support to start a process on Windows
|
||||
- [#2335][2335] Add lookup optimizations in DynELF
|
||||
- [#2334][2334] Speed up disasm commandline tool with colored output
|
||||
- [#2328][2328] Lookup using $PATHEXT file extensions in `which` on Windows
|
||||
- [#2189][2189] Explicitly define p64/u64 functions for IDE support
|
||||
- [#2339][2339] Fix: Allow setting attributes on gdb Breakpoints
|
||||
- [#2323][2323] Retry failed lookups after one week in libcdb
|
||||
- [#2325][2325] Match against local system libc first in libcdb
|
||||
- [#2336][2336] Add `ELF.stripped` and `ELF.debuginfo` properties
|
||||
- [#2161][2161] Add basic support for darwin shellcraft/asm/disasm/run_shellcode/run_assembly
|
||||
- [#2161][2161] Fix freebsd amd64 SyscallABI
|
||||
- [#2160][2161] Fix invalid shellcraft.mov on arm64
|
||||
- [#2284][2161] Fix invalid shellcraft.pushstr_array on arm64
|
||||
- [#2345][2345] Fix pwn constgrep when it matches a non-constant type
|
||||
- [#2338][2338] Fix: follow symlink for libs on ssh connection
|
||||
- [#2341][2341] Launch GDB correctly in iTerm on Mac
|
||||
- [#2268][2268] Add a `flatten` argument to `ssh.libs`
|
||||
- [#2347][2347] Fix/workaround Unicorn Engine 1GB limit that calls exit()
|
||||
- [#2233][2233] Fix gdb.debug: exe parameter now respected, allow empty argv
|
||||
- [#2373][2373] Fix displaying bright color variation in terminal output
|
||||
- [#2378][2378] Don't go though a shell in `gdb.debug`
|
||||
|
||||
[2242]: https://github.com/Gallopsled/pwntools/pull/2242
|
||||
[2277]: https://github.com/Gallopsled/pwntools/pull/2277
|
||||
[2281]: https://github.com/Gallopsled/pwntools/pull/2281
|
||||
[2293]: https://github.com/Gallopsled/pwntools/pull/2293
|
||||
[1763]: https://github.com/Gallopsled/pwntools/pull/1763
|
||||
[2307]: https://github.com/Gallopsled/pwntools/pull/2307
|
||||
[2309]: https://github.com/Gallopsled/pwntools/pull/2309
|
||||
[2308]: https://github.com/Gallopsled/pwntools/pull/2308
|
||||
[2279]: https://github.com/Gallopsled/pwntools/pull/2279
|
||||
[2310]: https://github.com/Gallopsled/pwntools/pull/2310
|
||||
[2335]: https://github.com/Gallopsled/pwntools/pull/2335
|
||||
[2334]: https://github.com/Gallopsled/pwntools/pull/2334
|
||||
[2328]: https://github.com/Gallopsled/pwntools/pull/2328
|
||||
[2189]: https://github.com/Gallopsled/pwntools/pull/2189
|
||||
[2339]: https://github.com/Gallopsled/pwntools/pull/2339
|
||||
[2323]: https://github.com/Gallopsled/pwntools/pull/2323
|
||||
[2325]: https://github.com/Gallopsled/pwntools/pull/2325
|
||||
[2336]: https://github.com/Gallopsled/pwntools/pull/2336
|
||||
[2161]: https://github.com/Gallopsled/pwntools/pull/2161
|
||||
[2345]: https://github.com/Gallopsled/pwntools/pull/2345
|
||||
[2338]: https://github.com/Gallopsled/pwntools/pull/2338
|
||||
[2341]: https://github.com/Gallopsled/pwntools/pull/2341
|
||||
[2268]: https://github.com/Gallopsled/pwntools/pull/2268
|
||||
[2347]: https://github.com/Gallopsled/pwntools/pull/2347
|
||||
[2233]: https://github.com/Gallopsled/pwntools/pull/2233
|
||||
[2373]: https://github.com/Gallopsled/pwntools/pull/2373
|
||||
[2378]: https://github.com/Gallopsled/pwntools/pull/2378
|
||||
|
||||
## 4.12.0
|
||||
|
||||
## 4.12.0 (`beta`)
|
||||
- [#2202][2202] Fix `remote` and `listen` in sagemath
|
||||
- [#2117][2117] Add -p (--prefix) and -s (--separator) arguments to `hex` command
|
||||
- [#2221][2221] Add shellcraft.sleep template wrapping SYS_nanosleep
|
||||
@@ -79,6 +358,8 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
- [#2212][2212] Add `--libc libc.so` argument to `pwn template` command
|
||||
- [#2257][2257] Allow creation of custom templates for `pwn template` command
|
||||
- [#2225][2225] Allow empty argv in ssh.process()
|
||||
- [#2349][2349] Fix term.readline omitting a trailing \n
|
||||
- [#2352][2352] add `RETURN_CONST` as an allowed `_const_code` in safeeval
|
||||
|
||||
[2202]: https://github.com/Gallopsled/pwntools/pull/2202
|
||||
[2117]: https://github.com/Gallopsled/pwntools/pull/2117
|
||||
@@ -87,10 +368,26 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
[2212]: https://github.com/Gallopsled/pwntools/pull/2212
|
||||
[2257]: https://github.com/Gallopsled/pwntools/pull/2257
|
||||
[2225]: https://github.com/Gallopsled/pwntools/pull/2225
|
||||
[2349]: https://github.com/Gallopsled/pwntools/pull/2349
|
||||
[2352]: https://github.com/Gallopsled/pwntools/pull/2352
|
||||
|
||||
## 4.11.0 (`stable`)
|
||||
## 4.11.1
|
||||
|
||||
- [#2185][2185] make fmtstr module able to create payload without $ notation
|
||||
- [#2271][2271] FIX: Generated shebang with path to python invalid if path contains spaces
|
||||
- [#2272][2272] Fix `tube.clean_and_log` not logging buffered data
|
||||
- [#2281][2281] FIX: Getting right amount of data for search fix
|
||||
- [#2287][2287] Fix `_countdown_handler` not invoking `timeout_change`
|
||||
- [#2294][2294] Fix atexit SEGV in aarch64 loader
|
||||
|
||||
[2271]: https://github.com/Gallopsled/pwntools/pull/2271
|
||||
[2272]: https://github.com/Gallopsled/pwntools/pull/2272
|
||||
[2281]: https://github.com/Gallopsled/pwntools/pull/2281
|
||||
[2287]: https://github.com/Gallopsled/pwntools/pull/2287
|
||||
[2294]: https://github.com/Gallopsled/pwntools/pull/2294
|
||||
|
||||
## 4.11.0
|
||||
|
||||
- [#2185][2185] make fmtstr module able to create payload without $ notation
|
||||
- [#2103][2103] Add search for libc binary by leaked function addresses `libcdb.search_by_symbol_offsets()`
|
||||
- [#2177][2177] Support for RISC-V 64-bit architecture
|
||||
- [#2186][2186] Enhance `ELF.nx` and `ELF.execstack`
|
||||
@@ -365,7 +662,7 @@ In memoriam — [Zach Riggle][zach] — long time contributor and maintainer of
|
||||
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
|
||||
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
|
||||
|
||||
## 4.1.7 (`stable`)
|
||||
## 4.1.7
|
||||
|
||||
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
|
||||
|
||||
|
||||
+1
-1
@@ -6,4 +6,4 @@ include *.md *.txt *.sh *.yml MANIFEST.in
|
||||
recursive-include docs *.rst *.png Makefile *.py *.txt
|
||||
recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh __doc__ *.mako
|
||||
recursive-include pwn *.py *.asm *.rst *.md *.txt *.sh
|
||||
recursive-exclude *.pyc
|
||||
global-exclude *.pyc
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
[](https://pypi.python.org/pypi/pwntools/)
|
||||
[](https://docs.pwntools.com/)
|
||||
[](https://travis-ci.org/Gallopsled/pwntools)
|
||||
[](https://github.com/Gallopsled/pwntools/actions/workflows/ci.yml?query=branch%3Adev)
|
||||
[](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
|
||||
[](http://choosealicense.com/licenses/mit/)
|
||||
@@ -33,13 +32,13 @@ To get you started, we've provided some example solutions for past CTF challenge
|
||||
|
||||
# Installation
|
||||
|
||||
Pwntools is best supported on 64-bit Ubuntu LTS releases (14.04, 16.04, 18.04, and 20.04). Most functionality should work on any Posix-like distribution (Debian, Arch, FreeBSD, OSX, etc.).
|
||||
Pwntools is best supported on 64-bit Ubuntu LTS releases (18.04, 20.04, 22.04, and 24.04). Most functionality should work on any Posix-like distribution (Debian, Arch, FreeBSD, OSX, etc.).
|
||||
|
||||
Python3 is suggested, but Pwntools still works with Python 2.7. Most of the functionality of pwntools is self-contained and Python-only. You should be able to get running quickly with
|
||||
|
||||
```sh
|
||||
apt-get update
|
||||
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
|
||||
sudo apt-get update
|
||||
sudo 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 pwntools
|
||||
```
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
capstone
|
||||
coveralls
|
||||
coverage[toml]
|
||||
python-dateutil
|
||||
doc2dash
|
||||
docutils<0.18
|
||||
docutils<0.18; python_version<'3'
|
||||
docutils>=0.18; python_version>='3'
|
||||
intervaltree
|
||||
isort
|
||||
mako>=1.0.0
|
||||
paramiko>=1.15.2
|
||||
pip>=6.0.8
|
||||
pyelftools>=0.2.3
|
||||
pyelftools>=0.29, <0.30; python_version<'3'
|
||||
pyelftools>=0.29; python_version>='3'
|
||||
pygments>=2.0
|
||||
pypandoc
|
||||
pyserial>=2.7
|
||||
@@ -17,6 +19,6 @@ psutil
|
||||
requests>=2.5.1
|
||||
ropgadget>=5.3
|
||||
sphinx==1.8.6; python_version<'3'
|
||||
sphinx>=4.5.0; python_version>='3'
|
||||
sphinx>=8.1.3, <9; python_version>='3'
|
||||
sphinx_rtd_theme
|
||||
sphinxcontrib-autoprogram<=0.1.5
|
||||
+8
-6
@@ -71,6 +71,7 @@ doctest_global_setup = '''
|
||||
import sys, os
|
||||
os.environ['PWNLIB_NOTERM'] = '1'
|
||||
os.environ['PWNLIB_RANDOMIZE'] = '0'
|
||||
import six
|
||||
import pwnlib.update
|
||||
import pwnlib.util.fiddling
|
||||
import logging
|
||||
@@ -97,8 +98,8 @@ pwnlib.context.ContextType.defaults['log_console'] = stdout()
|
||||
github_actions = os.environ.get('USER') == 'runner'
|
||||
travis_ci = os.environ.get('USER') == 'travis'
|
||||
local_doctest = os.environ.get('USER') == 'pwntools'
|
||||
branch_dev = os.environ.get('GITHUB_BASE_REF') == 'dev'
|
||||
skip_android = True
|
||||
is_python2 = six.PY2
|
||||
'''
|
||||
|
||||
autoclass_content = 'both'
|
||||
@@ -169,7 +170,7 @@ pygments_style = 'sphinx'
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@@ -265,8 +266,8 @@ latex_documents = [
|
||||
u'2016, Gallopsled et al.', 'manual'),
|
||||
]
|
||||
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3.8', None),
|
||||
'paramiko': ('https://paramiko-docs.readthedocs.org/en/2.1/', None)}
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3/', None),
|
||||
'paramiko': ('https://docs.paramiko.org/en/2.1/', None)}
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
@@ -359,6 +360,9 @@ def linkcode_resolve(domain, info):
|
||||
else:
|
||||
filename = info['module'].replace('.', '/') + '.py'
|
||||
|
||||
if isinstance(val, property):
|
||||
val = val.fget
|
||||
|
||||
if isinstance(val, (types.ModuleType, types.MethodType, types.FunctionType, types.TracebackType, types.FrameType, types.CodeType) + six.class_types):
|
||||
try:
|
||||
lines, first = inspect.getsourcelines(val)
|
||||
@@ -382,8 +386,6 @@ if build_dash:
|
||||
html_theme_path = [alabaster.get_path()]
|
||||
html_theme_options = { 'nosidebar' : True }
|
||||
|
||||
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
|
||||
|
||||
|
||||
# -- Customization to Sphinx autodoc generation --------------------------------------------
|
||||
import sphinx.ext.autodoc
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from pwn import *
|
||||
from glob import glob
|
||||
from pwnlib.elf.maps import CAT_PROC_MAPS_EXIT
|
||||
import shutil
|
||||
|
||||
:mod:`pwnlib.elf.elf` --- ELF Files
|
||||
===========================================================
|
||||
|
||||
@@ -77,12 +77,12 @@ Each of the ``pwntools`` modules is documented here.
|
||||
update
|
||||
useragents
|
||||
util/*
|
||||
windbg
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
testexample
|
||||
rop/call
|
||||
|
||||
.. only:: not dash
|
||||
|
||||
|
||||
+11
-10
@@ -15,6 +15,9 @@ following system libraries installed.
|
||||
|
||||
install/*
|
||||
|
||||
|
||||
Note: For Mac OS X you will need to have cmake ``brew install cmake`` and pkg-config ``brew install pkg-config`` installed.
|
||||
|
||||
Released Version
|
||||
-----------------
|
||||
|
||||
@@ -25,8 +28,8 @@ Python3
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt-get update
|
||||
$ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
|
||||
$ sudo apt-get update
|
||||
$ sudo 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 pwntools
|
||||
|
||||
@@ -40,8 +43,8 @@ Additionally, due to `pip` dropping support for Python2, a specfic version of `p
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt-get update
|
||||
$ apt-get install python python-pip python-dev git libssl-dev libffi-dev build-essential
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install python python-pip python-dev git libssl-dev libffi-dev build-essential
|
||||
$ python2 -m pip install --upgrade pip==20.3.4
|
||||
$ python2 -m pip install --upgrade pwntools
|
||||
|
||||
@@ -51,13 +54,11 @@ Command-Line Tools
|
||||
|
||||
When installed with ``sudo`` the above commands will install Pwntools' command-line tools to somewhere like ``/usr/bin``.
|
||||
|
||||
However, if you run as an unprivileged user, you may see a warning message that looks like this:
|
||||
However, if you run as an unprivileged user, you may see a warning message that looks like this::
|
||||
|
||||
.. code-block::
|
||||
|
||||
WARNING: The scripts asm, checksec, common, constgrep, cyclic, debug, disablenx, disasm,
|
||||
elfdiff, elfpatch, errno, hex, main, phd, pwn, pwnstrip, scramble, shellcraft, template,
|
||||
unhex, update and version are installed in '/home/user/.local/bin' which is not on PATH.
|
||||
WARNING: The scripts asm, checksec, common, constgrep, cyclic, debug, disablenx, disasm,
|
||||
elfdiff, elfpatch, errno, hex, main, phd, pwn, pwnstrip, scramble, shellcraft, template,
|
||||
unhex, update and version are installed in '/home/user/.local/bin' which is not on PATH.
|
||||
|
||||
Follow the instructions listed and add ``~/.local/bin`` to your ``$PATH`` environment variable.
|
||||
|
||||
|
||||
@@ -18,28 +18,35 @@ Ubuntu Xenial (16.04) has official packages for most architectures, and does not
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt-get install software-properties-common
|
||||
$ apt-add-repository ppa:pwntools/binutils
|
||||
$ apt-get update
|
||||
$ sudo apt-get install software-properties-common
|
||||
$ sudo apt-add-repository ppa:pwntools/binutils
|
||||
$ sudo apt-get update
|
||||
|
||||
Then, install the binutils for your architecture.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt-get install binutils-$ARCH-linux-gnu
|
||||
$ sudo apt-get install binutils-$ARCH-linux-gnu
|
||||
|
||||
Mac OS X
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Mac OS X is just as easy, but requires building binutils from source.
|
||||
However, we've made ``homebrew`` recipes to make this a single command.
|
||||
However, we've made ``homebrew`` recipes to make this just two commands.
|
||||
After installing `brew <https://brew.sh>`__, grab the appropriate
|
||||
recipe from our `binutils
|
||||
repo <https://github.com/Gallopsled/pwntools-binutils/>`__.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/macos/binutils-$ARCH.rb
|
||||
$ wget https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/macos/binutils-$ARCH.rb
|
||||
$ brew install ./binutils-$ARCH.rb
|
||||
|
||||
Windows
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Windows support is experimental. You can try installing a prebuilt version of binutils
|
||||
for your desired architecture from the `GNU Toolchains <https://gnutoolchains.com/>`__ project.
|
||||
|
||||
Alternate OSes
|
||||
^^^^^^^^^^^^^^^^
|
||||
@@ -54,7 +61,7 @@ OSes, ``binutils`` is simple to build by hand.
|
||||
V=2.38 # Binutils Version
|
||||
ARCH=arm # Target architecture
|
||||
|
||||
cd $TMP
|
||||
cd ${TMPDIR:-/tmp}
|
||||
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.gz
|
||||
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.gz.sig
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Ubuntu
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ apt-get install python-dev
|
||||
$ sudo apt-get install python-dev
|
||||
|
||||
Mac OS X
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -34,6 +34,8 @@ This exposes a standard interface to talk to processes, sockets, serial ports,
|
||||
and all manner of things, along with some nifty helpers for common tasks.
|
||||
For example, remote connections via :mod:`pwnlib.tubes.remote`.
|
||||
|
||||
::
|
||||
|
||||
>>> conn = remote('ftp.ubuntu.com',21)
|
||||
>>> conn.recvline() # doctest: +ELLIPSIS
|
||||
b'220 ...'
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
.. testsetup:: *
|
||||
|
||||
from pwn import *
|
||||
|
||||
|
||||
:mod:`pwnlib.protocols.adb` --- Protocol implementations
|
||||
========================================================
|
||||
|
||||
.. automodule:: pwnlib.protocols.adb
|
||||
:members:
|
||||
@@ -0,0 +1,19 @@
|
||||
.. testsetup:: *
|
||||
|
||||
from pwn import *
|
||||
context.clear(arch='riscv64')
|
||||
|
||||
:mod:`pwnlib.shellcraft.riscv64` --- Shellcode for RISCV64
|
||||
==========================================================
|
||||
|
||||
:mod:`pwnlib.shellcraft.riscv64`
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: pwnlib.shellcraft.riscv64
|
||||
:members:
|
||||
|
||||
:mod:`pwnlib.shellcraft.riscv64.linux`
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: pwnlib.shellcraft.riscv64.linux
|
||||
:members:
|
||||
@@ -0,0 +1,9 @@
|
||||
.. testsetup:: *
|
||||
|
||||
from pwn import *
|
||||
|
||||
:mod:`pwnlib.windbg` --- Working with WinDbg
|
||||
============================================
|
||||
|
||||
.. automodule:: pwnlib.windbg
|
||||
:members:
|
||||
+18
-12
@@ -11,18 +11,24 @@ Solution:
|
||||
"""
|
||||
|
||||
from pwn import *
|
||||
from multiprocessing import Process
|
||||
|
||||
os.system('''((
|
||||
echo prefix sometext ;
|
||||
echo prefix someothertext ;
|
||||
echo here comes the flag ;
|
||||
echo LostInTheInterTubes
|
||||
) | nc -l 1337) &
|
||||
''')
|
||||
def submit_data():
|
||||
with context.quiet:
|
||||
with listen(1337) as io:
|
||||
io.wait_for_connection()
|
||||
io.sendline(b'prefix sometext')
|
||||
io.sendline(b'prefix someothertext')
|
||||
io.sendline(b'here comes the flag')
|
||||
io.sendline(b'LostInTheInterTubes')
|
||||
|
||||
r = remote('localhost', 1337)
|
||||
atexit.register(r.clean_and_log)
|
||||
if __name__ == '__main__':
|
||||
p = Process(target=submit_data)
|
||||
p.start()
|
||||
|
||||
while True:
|
||||
line = r.recvline()
|
||||
print(re.findall(r'^prefix (\S+)$', line)[0])
|
||||
r = remote('localhost', 1337)
|
||||
atexit.register(r.clean_and_log)
|
||||
|
||||
while True:
|
||||
line = r.recvline()
|
||||
print(re.findall(br'^prefix (\S+)$', line)[0])
|
||||
+1
-1
@@ -4,5 +4,5 @@ Example showing `pwnlib.ui.options()`
|
||||
|
||||
from pwn import *
|
||||
|
||||
opts = [string.letters[x] for x in range(10)]
|
||||
opts = [string.ascii_letters[x] for x in range(12)]
|
||||
print('You choose "%s"' % opts[options('Pick one:', opts)])
|
||||
@@ -26,22 +26,26 @@ RUN apt-get update \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-dev \
|
||||
python-is-python3 \
|
||||
qemu-user-static \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
binutils-aarch64-linux-gnu \
|
||||
binutils-mips-linux-gnu \
|
||||
binutils-mipsel-linux-gnu \
|
||||
binutils-msp430 \
|
||||
binutils-powerpc-linux-gnu \
|
||||
binutils-powerpc64-linux-gnu \
|
||||
binutils-s390x-linux-gnu \
|
||||
binutils-sparc64-linux-gnu \
|
||||
binutils-riscv64-linux-gnu \
|
||||
tmux \
|
||||
patchelf \
|
||||
&& locale-gen en_US.UTF-8 \
|
||||
&& update-locale LANG=en_US.UTF-8 \
|
||||
&& PYTHONPATH=`echo /usr/share/python-wheels/pip-*.whl` python2.7 -m pip install --upgrade pip setuptools wheel \
|
||||
&& python2.7 -m pip install --upgrade pwntools \
|
||||
&& python3 -m pip install --upgrade pip \
|
||||
&& python3 -m pip install --upgrade pwntools \
|
||||
&& PYTHONPATH=`echo /usr/share/python-wheels/pip-*.whl` python2.7 -m pip install --no-cache-dir --upgrade pip setuptools wheel \
|
||||
&& python2.7 -m pip install --no-cache-dir --upgrade pwntools \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pip \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pwntools \
|
||||
&& PWNLIB_NOTERM=1 pwn update \
|
||||
&& useradd -m pwntools \
|
||||
&& passwd --delete --unlock pwntools \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM pwntools/pwntools:stable
|
||||
|
||||
USER root
|
||||
RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta \
|
||||
&& python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta
|
||||
RUN python2.7 -m pip install --no-cache-dir --upgrade git+https://github.com/Gallopsled/pwntools@beta \
|
||||
&& python3 -m pip install --no-cache-dir --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@beta
|
||||
RUN PWNLIB_NOTERM=1 pwn update
|
||||
USER pwntools
|
||||
@@ -5,4 +5,4 @@ RUN apt-get -y dist-upgrade
|
||||
RUN apt-get -y install python3 python3-pip
|
||||
RUN apt-get -y install git wget unzip
|
||||
|
||||
RUN pip3 install --upgrade git+https://github.com/Gallopsled/pwntools@dev
|
||||
RUN pip3 install --no-cache-dir --upgrade git+https://github.com/Gallopsled/pwntools@dev
|
||||
@@ -2,6 +2,6 @@ FROM pwntools/pwntools:stable
|
||||
|
||||
USER root
|
||||
RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev \
|
||||
&& python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev
|
||||
&& python3 -m pip install --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@dev
|
||||
RUN PWNLIB_NOTERM=1 pwn update
|
||||
USER pwntools
|
||||
@@ -34,7 +34,7 @@ RUN python2.7 -m pip install -U ipython ipdb \
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ="UTC"
|
||||
RUN sudo apt-get update && sudo -E apt-get install -y \
|
||||
tzdata \
|
||||
tzdata \
|
||||
ash \
|
||||
bash \
|
||||
bash-static \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM pwntools/pwntools:base
|
||||
|
||||
USER root
|
||||
RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@stable \
|
||||
&& python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@stable
|
||||
RUN python2.7 -m pip install --no-cache-dir --upgrade git+https://github.com/Gallopsled/pwntools@stable \
|
||||
&& python3 -m pip install --no-cache-dir --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@stable
|
||||
RUN PWNLIB_NOTERM=1 pwn update
|
||||
USER pwntools
|
||||
+9
-1
@@ -89,7 +89,15 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
colored_traceback.add_hook()
|
||||
try:
|
||||
colored_traceback.add_hook()
|
||||
except Exception:
|
||||
# Exception: curses.error
|
||||
# colored_traceback (curses.setupterm()) fails if TERM is unset.
|
||||
# This is not critical, so we just ignore it.
|
||||
# We cannot import `curses` for `curses.error` because it is not
|
||||
# available on all platforms (e.g. Windows).
|
||||
pass
|
||||
|
||||
# Equivalence with the default behavior of "from import *"
|
||||
# __all__ = [x for x in tuple(globals()) if not x.startswith('_')]
|
||||
@@ -36,6 +36,7 @@ __all__ = [
|
||||
'util',
|
||||
'update',
|
||||
'version',
|
||||
'windbg',
|
||||
]
|
||||
|
||||
from . import args
|
||||
+39
-6
@@ -48,6 +48,8 @@ class ABI(object):
|
||||
(32, 'mips', 'linux'): linux_mips,
|
||||
(32, 'powerpc', 'linux'): linux_ppc,
|
||||
(64, 'powerpc', 'linux'): linux_ppc64,
|
||||
(32, 'riscv32', 'linux'): linux_riscv32,
|
||||
(64, 'riscv64', 'linux'): linux_riscv64,
|
||||
(32, 'i386', 'freebsd'): freebsd_i386,
|
||||
(64, 'aarch64', 'freebsd'): freebsd_aarch64,
|
||||
(64, 'amd64', 'freebsd'): freebsd_amd64,
|
||||
@@ -58,6 +60,8 @@ class ABI(object):
|
||||
(64, 'powerpc', 'freebsd'): freebsd_ppc64,
|
||||
(32, 'i386', 'windows'): windows_i386,
|
||||
(64, 'amd64', 'windows'): windows_amd64,
|
||||
(64, 'amd64', 'darwin'): darwin_amd64,
|
||||
(64, 'aarch64', 'darwin'): darwin_aarch64,
|
||||
}[(context.bits, context.arch, context.os)]
|
||||
|
||||
@staticmethod
|
||||
@@ -76,6 +80,8 @@ class ABI(object):
|
||||
(64, 'aarch64', 'linux'): linux_aarch64_syscall,
|
||||
(32, 'powerpc', 'linux'): linux_ppc_syscall,
|
||||
(64, 'powerpc', 'linux'): linux_ppc64_syscall,
|
||||
(32, 'riscv32', 'linux'): linux_riscv32_syscall,
|
||||
(64, 'riscv64', 'linux'): linux_riscv64_syscall,
|
||||
(32, 'i386', 'freebsd'): freebsd_i386_syscall,
|
||||
(64, 'amd64', 'freebsd'): freebsd_amd64_syscall,
|
||||
(64, 'aarch64', 'freebsd'): freebsd_aarch64_syscall,
|
||||
@@ -85,6 +91,8 @@ class ABI(object):
|
||||
(64, 'aarch64', 'freebsd'): freebsd_aarch64_syscall,
|
||||
(32, 'powerpc', 'freebsd'): freebsd_ppc_syscall,
|
||||
(64, 'powerpc', 'freebsd'): freebsd_ppc64_syscall,
|
||||
(64, 'amd64', 'darwin'): darwin_amd64_syscall,
|
||||
(64, 'aarch64', 'darwin'): darwin_aarch64_syscall,
|
||||
}[(context.bits, context.arch, context.os)]
|
||||
|
||||
@staticmethod
|
||||
@@ -99,13 +107,18 @@ class ABI(object):
|
||||
(32, 'arm', 'linux'): linux_arm_sigreturn,
|
||||
(32, 'thumb', 'linux'): linux_arm_sigreturn,
|
||||
(64, 'aarch64', 'linux'): linux_aarch64_sigreturn,
|
||||
(32, 'riscv32', 'linux'): linux_riscv32_sigreturn,
|
||||
(64, 'riscv64', 'linux'): linux_riscv64_sigreturn,
|
||||
(32, 'i386', 'freebsd'): freebsd_i386_sigreturn,
|
||||
(64, 'amd64', 'freebsd'): freebsd_amd64_sigreturn,
|
||||
(32, 'arm', 'freebsd'): freebsd_arm_sigreturn,
|
||||
(32, 'thumb', 'freebsd'): freebsd_arm_sigreturn,
|
||||
(64, 'aarch64', 'freebsd'): freebsd_aarch64_sigreturn,
|
||||
(64, 'amd64', 'darwin'): darwin_amd64_sigreturn,
|
||||
(64, 'aarch64', 'darwin'): darwin_aarch64_sigreturn,
|
||||
}[(context.bits, context.arch, context.os)]
|
||||
|
||||
|
||||
class SyscallABI(ABI):
|
||||
"""
|
||||
The syscall ABI treats the syscall number as the zeroth argument,
|
||||
@@ -115,6 +128,7 @@ class SyscallABI(ABI):
|
||||
super(SyscallABI, self).__init__(*a, **kw)
|
||||
self.syscall_register = self.register_arguments[0]
|
||||
|
||||
|
||||
class SigreturnABI(SyscallABI):
|
||||
"""
|
||||
The sigreturn ABI is similar to the syscall ABI, except that
|
||||
@@ -132,6 +146,8 @@ linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3'], 16, 0)
|
||||
linux_mips = ABI('$sp', ['$a0','$a1','$a2','$a3'], 4, 0)
|
||||
linux_ppc = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 4, 0)
|
||||
linux_ppc64 = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 8, 0)
|
||||
linux_riscv32 = ABI('sp', ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7'], 8, 0)
|
||||
linux_riscv64 = ABI('sp', ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7'], 8, 0)
|
||||
|
||||
sysv_i386 = linux_i386
|
||||
sysv_amd64 = linux_amd64
|
||||
@@ -140,24 +156,33 @@ sysv_aarch64 = linux_aarch64
|
||||
sysv_mips = linux_mips
|
||||
sysv_ppc = linux_ppc
|
||||
sysv_ppc64 = linux_ppc64
|
||||
sysv_riscv32 = linux_riscv32
|
||||
sysv_riscv64 = linux_riscv64
|
||||
|
||||
# Docs: https://man7.org/linux/man-pages/man2/syscall.2.html
|
||||
linux_i386_syscall = SyscallABI('esp', ['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp'], 4, 0)
|
||||
linux_amd64_syscall = SyscallABI('rsp', ['rax', 'rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
|
||||
linux_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
|
||||
linux_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6'], 16, 0)
|
||||
linux_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
|
||||
linux_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5'], 16, 0)
|
||||
linux_mips_syscall = SyscallABI('$sp', ['$v0','$a0','$a1','$a2','$a3'], 4, 0)
|
||||
linux_ppc_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 4, 0)
|
||||
linux_ppc64_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 8, 0)
|
||||
linux_ppc_syscall = SyscallABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 4, 0)
|
||||
linux_ppc64_syscall = SyscallABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8'], 8, 0)
|
||||
linux_riscv32_syscall = SyscallABI('sp', ['a7', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5'], 4, 0)
|
||||
linux_riscv64_syscall = SyscallABI('sp', ['a7', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5'], 8, 0)
|
||||
|
||||
linux_i386_sigreturn = SigreturnABI('esp', ['eax'], 4, 0)
|
||||
linux_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 4, 0)
|
||||
linux_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 8, 0)
|
||||
linux_arm_sigreturn = SigreturnABI('sp', ['r7'], 4, 0)
|
||||
linux_aarch64_sigreturn = SigreturnABI('sp', ['x8'], 16, 0)
|
||||
linux_riscv32_sigreturn = SigreturnABI('sp', ['a7'], 4, 0)
|
||||
linux_riscv64_sigreturn = SigreturnABI('sp', ['a7'], 8, 0)
|
||||
|
||||
sysv_i386_sigreturn = linux_i386_sigreturn
|
||||
sysv_amd64_sigreturn = linux_amd64_sigreturn
|
||||
sysv_arm_sigreturn = linux_arm_sigreturn
|
||||
sysv_aarch64_sigreturn = linux_aarch64_sigreturn
|
||||
sysv_riscv32_sigreturn = linux_riscv32_sigreturn
|
||||
sysv_riscv64_sigreturn = linux_riscv64_sigreturn
|
||||
|
||||
freebsd_i386 = sysv_i386
|
||||
freebsd_amd64 = sysv_amd64
|
||||
@@ -168,7 +193,7 @@ freebsd_ppc = sysv_ppc
|
||||
freebsd_ppc64 = sysv_ppc64
|
||||
|
||||
freebsd_i386_syscall = SyscallABI('esp', ['eax'], 4, 0)
|
||||
freebsd_amd64_syscall = SyscallABI('rsp', ['rax','rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
|
||||
freebsd_amd64_syscall = SyscallABI('rsp', ['rax','rdi','rsi','rdx','r10','r8','r9'], 8, 0)
|
||||
freebsd_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3'], 8, 0)
|
||||
freebsd_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3'], 16, 0)
|
||||
freebsd_mips_syscall = SyscallABI('$sp', ['$v0','$a0','$a1','$a2','$a3'], 4, 0)
|
||||
@@ -182,3 +207,11 @@ freebsd_aarch64_sigreturn = sysv_aarch64_sigreturn
|
||||
|
||||
windows_i386 = ABI('esp', [], 4, 0)
|
||||
windows_amd64 = ABI('rsp', ['rcx','rdx','r8','r9'], 32, 32)
|
||||
|
||||
darwin_aarch64 = sysv_aarch64
|
||||
darwin_aarch64_syscall = SyscallABI('sp', ['x16', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5'], 16, 0)
|
||||
darwin_aarch64_sigreturn = SigreturnABI('sp', ['x16'], 16, 0)
|
||||
|
||||
darwin_amd64 = sysv_amd64
|
||||
darwin_amd64_syscall = SyscallABI('rsp', ['rax', 'rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
|
||||
darwin_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 8, 0)
|
||||
+36
-11
@@ -66,6 +66,7 @@ from pwnlib import tubes
|
||||
from pwnlib.context import LocalContext
|
||||
from pwnlib.context import context
|
||||
from pwnlib.device import Device
|
||||
from pwnlib.exception import PwnlibException
|
||||
from pwnlib.log import getLogger
|
||||
from pwnlib.protocols.adb import AdbClient
|
||||
from pwnlib.util.packing import _decode
|
||||
@@ -122,7 +123,7 @@ def current_device(any=False):
|
||||
|
||||
>>> device = adb.current_device(any=True)
|
||||
>>> device # doctest: +ELLIPSIS
|
||||
AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_...phone_armv7', model='sdk ...phone armv7', device='generic')
|
||||
AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_...phone..._...', model='...', device='...')
|
||||
>>> device.port
|
||||
'emulator'
|
||||
"""
|
||||
@@ -252,13 +253,13 @@ class AdbDevice(Device):
|
||||
|
||||
>>> device = adb.wait_for_device()
|
||||
>>> device.arch
|
||||
'arm'
|
||||
'amd64'
|
||||
>>> device.bits
|
||||
32
|
||||
64
|
||||
>>> device.os
|
||||
'android'
|
||||
>>> device.product # doctest: +ELLIPSIS
|
||||
'sdk_...phone_armv7'
|
||||
'sdk_...phone..._...'
|
||||
>>> device.serial
|
||||
'emulator-5554'
|
||||
"""
|
||||
@@ -879,7 +880,7 @@ def which(name, all = False, *a, **kw):
|
||||
>>> adb.which('sh')
|
||||
'/system/bin/sh'
|
||||
>>> adb.which('sh', all=True)
|
||||
['/system/bin/sh']
|
||||
['/system/bin/sh', '/vendor/bin/sh']
|
||||
|
||||
>>> adb.which('foobar') is None
|
||||
True
|
||||
@@ -987,7 +988,7 @@ def proc_exe(pid):
|
||||
:skipif: skip_android
|
||||
|
||||
>>> adb.proc_exe(1)
|
||||
b'/init'
|
||||
b'/system/bin/init'
|
||||
"""
|
||||
with context.quiet:
|
||||
io = process(['realpath','/proc/%d/exe' % pid])
|
||||
@@ -1274,8 +1275,28 @@ properties = Property()
|
||||
|
||||
def _build_date():
|
||||
"""Returns the build date in the form YYYY-MM-DD as a string"""
|
||||
import datetime
|
||||
|
||||
# Use ro.build.date.utc (integer epoch seconds) which is set by the
|
||||
# AOSP build system and available on all standard Android devices.
|
||||
# This avoids ro.build.date which is locale-dependent and can contain
|
||||
# non-ASCII characters that dateutil cannot parse. See #2513.
|
||||
utc = getprop('ro.build.date.utc')
|
||||
if utc and utc.strip().isdigit():
|
||||
try:
|
||||
as_datetime = datetime.datetime.fromtimestamp(int(utc.strip()), dateutil.tz.UTC)
|
||||
return as_datetime.strftime('%Y-%b-%d')
|
||||
except (OSError, OverflowError, ValueError):
|
||||
pass
|
||||
|
||||
# Fallback for non-standard builds missing ro.build.date.utc.
|
||||
as_string = getprop('ro.build.date')
|
||||
as_datetime = dateutil.parser.parse(as_string)
|
||||
if not as_string:
|
||||
return ''
|
||||
try:
|
||||
as_datetime = dateutil.parser.parse(as_string)
|
||||
except (ValueError, OverflowError):
|
||||
return as_string
|
||||
return as_datetime.strftime('%Y-%b-%d')
|
||||
|
||||
def find_ndk_project_root(source):
|
||||
@@ -1364,7 +1385,7 @@ def compile(source):
|
||||
>>> filename = adb.compile(temp)
|
||||
>>> sent = adb.push(filename, "/data/local/tmp")
|
||||
>>> adb.process(sent).recvall() # doctest: +ELLIPSIS
|
||||
b'... /system/bin/linker\n...'
|
||||
b'... /system/lib64/libc++.so\n...'
|
||||
"""
|
||||
|
||||
ndk_build = misc.which('ndk-build')
|
||||
@@ -1490,8 +1511,9 @@ class Partitions(object):
|
||||
@context.quietfunc
|
||||
def by_name_dir(self):
|
||||
try:
|
||||
return next(find('/dev/block/platform','by-name'))
|
||||
except StopIteration:
|
||||
with context.local(log_level=logging.FATAL):
|
||||
return next(find('/dev/block/platform','by-name'))
|
||||
except (StopIteration, PwnlibException):
|
||||
return '/dev/block'
|
||||
|
||||
@context.quietfunc
|
||||
@@ -1591,7 +1613,10 @@ def uninstall(package, *arguments):
|
||||
@context.quietfunc
|
||||
def packages():
|
||||
"""Returns a list of packages installed on the system"""
|
||||
packages = process(['pm', 'list', 'packages']).recvall()
|
||||
# Decodes the received bytes as UTF-8 per:
|
||||
# https://developer.android.com/reference/java/nio/charset/Charset#defaultCharset()
|
||||
# where it is specified that UTF-8 is the default charset for Android.
|
||||
packages = process(['pm', 'list', 'packages']).recvall().decode('utf-8')
|
||||
return [line.split('package:', 1)[-1] for line in packages.splitlines()]
|
||||
|
||||
@context.quietfunc
|
||||
|
||||
@@ -159,6 +159,11 @@ def STDERR(v):
|
||||
"""Sends logging to ``stderr`` by default, instead of ``stdout``"""
|
||||
context.log_console = sys.stderr
|
||||
|
||||
def LOCAL_LIBCDB(v):
|
||||
"""Sets path to local libc-database via ``context.local_libcdb``, e.g.
|
||||
``LOCAL_LIBCDB='/path/to/libc-databse'``"""
|
||||
context.local_libcdb = v
|
||||
|
||||
hooks = {
|
||||
'LOG_LEVEL': LOG_LEVEL,
|
||||
'LOG_FILE': LOG_FILE,
|
||||
@@ -170,6 +175,7 @@ hooks = {
|
||||
'NOASLR': NOASLR,
|
||||
'NOPTRACE': NOPTRACE,
|
||||
'STDERR': STDERR,
|
||||
'LOCAL_LIBCDB': LOCAL_LIBCDB,
|
||||
}
|
||||
|
||||
def initialize():
|
||||
|
||||
+141
-9
@@ -59,6 +59,9 @@ from pwnlib import shellcraft
|
||||
from pwnlib.context import LocalContext
|
||||
from pwnlib.context import context
|
||||
from pwnlib.log import getLogger
|
||||
from pwnlib.util.hashes import sha1sumhex
|
||||
from pwnlib.util.packing import _encode
|
||||
from pwnlib.version import __version__
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
@@ -167,6 +170,8 @@ def which_binutils(util, check_version=False):
|
||||
'.../bin/arm-...-as'
|
||||
>>> which_binutils('as', arch='powerpc') #doctest: +ELLIPSIS
|
||||
'.../bin/powerpc...-as'
|
||||
>>> which_binutils('as', arch='mips', endianness='little') #doctest: +ELLIPSIS
|
||||
'.../bin/mipsel...-as'
|
||||
>>> which_binutils('as', arch='msp430') #doctest: +SKIP
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
@@ -175,6 +180,12 @@ def which_binutils(util, check_version=False):
|
||||
"""
|
||||
arch = context.arch
|
||||
|
||||
# Handle endianness-specific naming for mips/mips64
|
||||
arch = {
|
||||
('mips', 'little'): 'mipsel',
|
||||
('mips64', 'little'): 'mips64el',
|
||||
}.get((arch, context.endianness), arch)
|
||||
|
||||
# Fix up pwntools vs Debian triplet naming, and account
|
||||
# for 'thumb' being its own pwntools architecture.
|
||||
arches = [arch] + {
|
||||
@@ -182,11 +193,14 @@ def which_binutils(util, check_version=False):
|
||||
'i386': ['x86_64', 'amd64'],
|
||||
'i686': ['x86_64', 'amd64'],
|
||||
'amd64': ['x86_64', 'i386'],
|
||||
'mips64': ['mips'],
|
||||
'mips': ['mipsel'],
|
||||
'mipsel': ['mips'],
|
||||
'mips64': ['mips', 'mipsel'],
|
||||
'mips64el': ['mipsel', 'mips'],
|
||||
'powerpc64': ['powerpc'],
|
||||
'sparc64': ['sparc'],
|
||||
'riscv32': ['riscv32', 'riscv64', 'riscv'],
|
||||
'riscv64': ['riscv64', 'riscv32', 'riscv'],
|
||||
'riscv32': ['riscv64', 'riscv'],
|
||||
'riscv64': ['riscv32', 'riscv'],
|
||||
}.get(arch, [])
|
||||
|
||||
# If one of the candidate architectures matches the native
|
||||
@@ -206,6 +220,9 @@ def which_binutils(util, check_version=False):
|
||||
if platform.system() == 'Darwin':
|
||||
utils = ['g'+util, util]
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
utils = [util + '.exe']
|
||||
|
||||
for arch in arches:
|
||||
for gutil in utils:
|
||||
# e.g. objdump
|
||||
@@ -220,7 +237,7 @@ def which_binutils(util, check_version=False):
|
||||
'%s-%s' % (arch, gutil)]
|
||||
|
||||
for pattern in patterns:
|
||||
for dir in environ['PATH'].split(':'):
|
||||
for dir in environ['PATH'].split(os.pathsep):
|
||||
for res in sorted(glob(path.join(dir, pattern))):
|
||||
if check_version:
|
||||
ver = check_binutils_version(res)
|
||||
@@ -457,18 +474,24 @@ def cpp(shellcode):
|
||||
>>> cpp("SYS_setresuid", os = "freebsd")
|
||||
'311\n'
|
||||
"""
|
||||
if platform.system() == 'Windows':
|
||||
cpp = which_binutils('cpp')
|
||||
else:
|
||||
cpp = 'cpp'
|
||||
|
||||
code = _include_header() + shellcode
|
||||
cmd = [
|
||||
'cpp',
|
||||
cpp,
|
||||
'-Wno-unused-command-line-argument',
|
||||
'-C',
|
||||
'-nostdinc',
|
||||
'-undef',
|
||||
'-P',
|
||||
'-I' + _incdir,
|
||||
'/dev/stdin'
|
||||
]
|
||||
return _run(cmd, code).strip('\n').rstrip() + '\n'
|
||||
|
||||
|
||||
@LocalContext
|
||||
def make_elf_from_assembly(assembly,
|
||||
vma=None,
|
||||
@@ -651,6 +674,68 @@ def make_elf(data,
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
@LocalContext
|
||||
def make_macho_from_assembly(shellcode):
|
||||
return make_macho(shellcode, is_shellcode=True)
|
||||
|
||||
|
||||
@LocalContext
|
||||
def make_macho(data, is_shellcode=False):
|
||||
prefix = []
|
||||
if context.arch == 'amd64':
|
||||
prefix = [
|
||||
'.intel_syntax noprefix',
|
||||
]
|
||||
prefix.extend([
|
||||
'.text',
|
||||
'.global _start',
|
||||
'_start:',
|
||||
'.p2align 2',
|
||||
])
|
||||
code = ''
|
||||
code += '\n'.join(prefix) + '\n'
|
||||
if is_shellcode:
|
||||
code += cpp(data)
|
||||
else:
|
||||
code += '.string "%s"' % ''.join('\\x%02x' % c for c in bytearray(data))
|
||||
|
||||
log.debug('Assembling\n%s' % code)
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix = 'pwn-asm-')
|
||||
step1 = path.join(tmpdir, 'step1')
|
||||
step2 = path.join(tmpdir, 'step2')
|
||||
step3 = path.join(tmpdir, 'step3')
|
||||
|
||||
with open(step1, 'w') as fd:
|
||||
fd.write(code)
|
||||
|
||||
assembler = [
|
||||
'/usr/bin/as',
|
||||
]
|
||||
asflags = [
|
||||
'-mmacosx-version-min=11.0',
|
||||
'-o', step2, step1,
|
||||
]
|
||||
_run(assembler + asflags)
|
||||
|
||||
linker = [
|
||||
'/usr/bin/ld',
|
||||
]
|
||||
ldflags = [
|
||||
'-macos_version_min', '11.0',
|
||||
'-l', 'System',
|
||||
'-e', '_start',
|
||||
'-L', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib',
|
||||
'-o', step3, step2,
|
||||
]
|
||||
_run(linker + ldflags)
|
||||
|
||||
os.chmod(step3, 0o755)
|
||||
|
||||
return step3
|
||||
|
||||
|
||||
@LocalContext
|
||||
def asm(shellcode, vma = 0, extract = True, shared = False):
|
||||
r"""asm(code, vma = 0, extract = True, shared = False, ...) -> str
|
||||
@@ -688,8 +773,23 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
|
||||
b'0@*\x00'
|
||||
>>> asm("la %r0, 42", arch = 's390', bits=64)
|
||||
b'A\x00\x00*'
|
||||
|
||||
The output is cached:
|
||||
|
||||
>>> start = time.time()
|
||||
>>> asm("lea rax, [rip+0]", arch = 'amd64', cache_dir = None) # force uncached time
|
||||
b'H\x8d\x05\x00\x00\x00\x00'
|
||||
>>> uncached_time = time.time() - start
|
||||
>>> asm("lea rax, [rip+0]", arch = 'amd64') # cache it
|
||||
b'H\x8d\x05\x00\x00\x00\x00'
|
||||
>>> start = time.time()
|
||||
>>> asm("lea rax, [rip+0]", arch = 'amd64')
|
||||
b'H\x8d\x05\x00\x00\x00\x00'
|
||||
>>> cached_time = time.time() - start
|
||||
>>> uncached_time > cached_time
|
||||
True
|
||||
"""
|
||||
result = ''
|
||||
result = b''
|
||||
|
||||
assembler = _assembler()
|
||||
linker = _linker()
|
||||
@@ -700,6 +800,30 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
|
||||
|
||||
log.debug('Assembling\n%s' % code)
|
||||
|
||||
cache_file = None
|
||||
if context.cache_dir:
|
||||
cache_dir = os.path.join(context.cache_dir, 'asm-cache')
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
# Include the context in the hash in addition to the shellcode
|
||||
hash_params = '{}_{}_{}_{}'.format(vma, extract, shared, __version__)
|
||||
fingerprint_params = _encode(code) + _encode(hash_params) + _encode(' '.join(assembler)) + _encode(' '.join(linker)) + _encode(' '.join(objcopy))
|
||||
asm_hash = sha1sumhex(fingerprint_params)
|
||||
cache_file = os.path.join(cache_dir, asm_hash)
|
||||
if os.path.exists(cache_file):
|
||||
log.debug('Using cached assembly output from %r', cache_file)
|
||||
if extract:
|
||||
with open(cache_file, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
# Create a temporary copy of the cached file to avoid modification.
|
||||
tmpdir = tempfile.mkdtemp(prefix = 'pwn-asm-')
|
||||
atexit.register(shutil.rmtree, tmpdir)
|
||||
step3 = os.path.join(tmpdir, 'step3')
|
||||
shutil.copy(cache_file, step3)
|
||||
return step3
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix = 'pwn-asm-')
|
||||
step1 = path.join(tmpdir, 'step1')
|
||||
step2 = path.join(tmpdir, 'step2')
|
||||
@@ -747,6 +871,8 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
|
||||
shutil.copy(step2, step3)
|
||||
|
||||
if not extract:
|
||||
if cache_file is not None:
|
||||
shutil.copy(step3, cache_file)
|
||||
return step3
|
||||
|
||||
_run(objcopy + [step3, step4])
|
||||
@@ -760,6 +886,10 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
|
||||
else:
|
||||
atexit.register(lambda: shutil.rmtree(tmpdir))
|
||||
|
||||
if cache_file is not None and result != b'':
|
||||
with open(cache_file, 'wb') as f:
|
||||
f.write(result)
|
||||
|
||||
return result
|
||||
|
||||
@LocalContext
|
||||
@@ -792,8 +922,8 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
|
||||
0: b8 17 00 00 00 mov eax, 0x17
|
||||
>>> print(disasm(unhex('48c7c017000000'), arch = 'amd64'))
|
||||
0: 48 c7 c0 17 00 00 00 mov rax, 0x17
|
||||
>>> print(disasm(unhex('04001fe552009000'), arch = 'arm'))
|
||||
0: e51f0004 ldr r0, [pc, #-4] ; 0x4
|
||||
>>> print(disasm(unhex('04001fe552009000'), arch = 'arm')) # doctest: +ELLIPSIS
|
||||
0: e51f0004 ldr r0, [pc, #-4] ...
|
||||
4: 00900052 addseq r0, r0, r2, asr r0
|
||||
>>> print(disasm(unhex('4ff00500'), arch = 'thumb', bits=32))
|
||||
0: f04f 0005 mov.w r0, #5
|
||||
@@ -860,6 +990,8 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
|
||||
|
||||
|
||||
lines = []
|
||||
|
||||
# Note: those patterns are also used in pwnlib/commandline/disasm.py
|
||||
pattern = '^( *[0-9a-f]+: *)', '((?:[0-9a-f]+ )+ *)', '(.*)'
|
||||
if not byte:
|
||||
pattern = pattern[::2]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
__all__ = [
|
||||
'asm',
|
||||
'checksec',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -138,4 +137,4 @@ def main(args):
|
||||
args.output.write(b'\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -36,7 +35,10 @@ def main(args):
|
||||
return
|
||||
|
||||
for f in files:
|
||||
e = ELF(f.name)
|
||||
try:
|
||||
e = ELF(f.name)
|
||||
except Exception as e:
|
||||
print("{name}: {error}".format(name=f.name, error=e))
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
common.main(__file__, main)
|
||||
@@ -25,8 +25,25 @@ parser = argparse.ArgumentParser(description='Pwntools Command-line Interface',
|
||||
prog='pwn')
|
||||
parser_commands = parser.add_subparsers(dest='command')
|
||||
|
||||
def main(file=sys.argv[0]):
|
||||
import pwnlib.commandline.main
|
||||
def main(file=sys.argv[0], command_main=None):
|
||||
name = os.path.splitext(os.path.basename(file))[0]
|
||||
if command_main is None:
|
||||
import importlib
|
||||
command_main = importlib.import_module('pwnlib.commandline.%s' % name).main
|
||||
sys.argv.insert(1, name)
|
||||
pwnlib.commandline.main.main()
|
||||
entrypoint({name: command_main})
|
||||
|
||||
def deprecated_main():
|
||||
file=sys.argv[0]
|
||||
name = os.path.splitext(os.path.basename(file))[0]
|
||||
import warnings
|
||||
warnings.warn("The '%s' command is deprecated and will be removed in a future version. Please use 'pwn %s' instead." % (name, name), DeprecationWarning, stacklevel=2)
|
||||
main(file)
|
||||
|
||||
def entrypoint(commands):
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_usage()
|
||||
sys.exit()
|
||||
args = parser.parse_args()
|
||||
with context.local(log_console = sys.stderr):
|
||||
commands[args.command](args)
|
||||
Executable → Regular
+7
-4
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -92,9 +91,13 @@ def main(args):
|
||||
if not matcher.search(k):
|
||||
continue
|
||||
|
||||
# Check if the value has proper type
|
||||
val = getattr(mod, k)
|
||||
if not isinstance(val, pwnlib.constants.constant.Constant):
|
||||
continue
|
||||
|
||||
# Check the constant
|
||||
if constant is not None:
|
||||
val = getattr(mod, k)
|
||||
if args.mask_mode:
|
||||
if constant & val != val:
|
||||
continue
|
||||
@@ -103,7 +106,7 @@ def main(args):
|
||||
continue
|
||||
|
||||
# Append it
|
||||
out.append((getattr(mod, k), k))
|
||||
out.append((val, k))
|
||||
maxlen = max(len(k), maxlen)
|
||||
|
||||
# Output all matching constants
|
||||
@@ -130,4 +133,4 @@ def main(args):
|
||||
print('(%s) == %s' % (' | '.join(k for v, k in good), args.constant))
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -108,4 +107,4 @@ def main(args):
|
||||
out.write(b'\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -103,4 +102,4 @@ def main(args):
|
||||
gdb.debug(target, gdbscript=gdbscript, sysroot=args.sysroot).interactive()
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -25,4 +24,4 @@ def main(args):
|
||||
ELF(e.path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
@@ -77,21 +77,37 @@ def main(args):
|
||||
from pygments.formatters import TerminalFormatter
|
||||
from pwnlib.lexer import PwntoolsLexer
|
||||
|
||||
offsets = disasm(dat, vma=safeeval.const(args.address), instructions=False, byte=False)
|
||||
bytes = disasm(dat, vma=safeeval.const(args.address), instructions=False, offset=False)
|
||||
instrs = disasm(dat, vma=safeeval.const(args.address), byte=False, offset=False)
|
||||
# instrs = highlight(instrs, PwntoolsLexer(), TerminalFormatter())
|
||||
dis = disasm(dat, vma=safeeval.const(args.address))
|
||||
|
||||
# Note: those patterns are copied from disasm function
|
||||
pattern = '^( *[0-9a-f]+: *)((?:[0-9a-f]+ )+ *)(.*)'
|
||||
lines = []
|
||||
for line in dis.splitlines():
|
||||
match = re.search(pattern, line)
|
||||
if not match:
|
||||
# Append as one element tuple
|
||||
lines.append((line,))
|
||||
continue
|
||||
|
||||
groups = match.groups()
|
||||
o, b, i = groups
|
||||
|
||||
lines.append((o, b, i))
|
||||
|
||||
|
||||
highlight_bytes = lambda t: ''.join(map(lambda x: x.replace('00', text.red('00')).replace('0a', text.red('0a')), group(2, t)))
|
||||
for o,b,i in zip(*map(str.splitlines, (offsets, bytes, instrs))):
|
||||
b = ' '.join(highlight_bytes(bb) for bb in b.split(' '))
|
||||
i = highlight(i.strip(), PwntoolsLexer(), TerminalFormatter()).strip()
|
||||
i = i.replace(',',', ')
|
||||
|
||||
print(o,b,i)
|
||||
for line in lines:
|
||||
if len(line) == 3:
|
||||
o, b, i = line
|
||||
b = ' '.join(highlight_bytes(bb) for bb in b.split(' '))
|
||||
i = highlight(i.strip(), PwntoolsLexer(), TerminalFormatter()).strip()
|
||||
i = i.replace(',',', ')
|
||||
print(o,b,i)
|
||||
else:
|
||||
print(line[0])
|
||||
return
|
||||
|
||||
print(disasm(dat, vma=safeeval.const(args.address)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -60,4 +59,4 @@ def main(a):
|
||||
print(diff(x, y))
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -35,4 +34,4 @@ def main(a):
|
||||
getattr(sys.stdout, 'buffer', sys.stdout).write(elf.get_data())
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -46,4 +46,4 @@ def main(args):
|
||||
print(os.strerror(value))
|
||||
|
||||
if __name__ == '__main__':
|
||||
common.main(__file__)
|
||||
common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -7,6 +6,7 @@ import sys
|
||||
|
||||
from pwnlib.commandline import common
|
||||
from pwnlib.util.fiddling import enhex
|
||||
from pwnlib.util.lists import group
|
||||
|
||||
parser = common.parser_commands.add_parser(
|
||||
'hex',
|
||||
@@ -16,14 +16,38 @@ parser = common.parser_commands.add_parser(
|
||||
parser.add_argument('data', nargs='*',
|
||||
help='Data to convert into hex')
|
||||
|
||||
parser.add_argument(
|
||||
'-p', '--prefix',
|
||||
metavar = 'prefix',
|
||||
type = str,
|
||||
default = '',
|
||||
help = 'Insert a prefix before each byte',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-s', '--separator',
|
||||
metavar = 'separator',
|
||||
type = str,
|
||||
default = '',
|
||||
help = 'Add a separator between each byte',
|
||||
)
|
||||
|
||||
def format_hex(hex_string, prefix, separator):
|
||||
return separator.join([prefix + x for x in group(2, hex_string)])
|
||||
|
||||
def main(args):
|
||||
if not args.data:
|
||||
print(enhex(getattr(sys.stdin, 'buffer', sys.stdin).read()))
|
||||
encoded = enhex(getattr(sys.stdin, 'buffer', sys.stdin).read())
|
||||
else:
|
||||
data = ' '.join(args.data)
|
||||
if not hasattr(data, 'decode'):
|
||||
data = data.encode('utf-8', 'surrogateescape')
|
||||
print(enhex(data))
|
||||
encoded = enhex(data)
|
||||
|
||||
if args.prefix or args.separator:
|
||||
encoded = format_hex(encoded, args.prefix, args.separator)
|
||||
|
||||
print(encoded)
|
||||
|
||||
if __name__ == '__main__':
|
||||
common.main(__file__)
|
||||
common.main(__file__, main)
|
||||
+113
-69
@@ -37,19 +37,12 @@ lookup_parser.add_argument(
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--download-libc',
|
||||
'-d', '--download-libc',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
help = 'Attempt to download the matching libc.so'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--unstrip',
|
||||
action = 'store_true',
|
||||
default = True,
|
||||
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--no-unstrip',
|
||||
action = 'store_false',
|
||||
@@ -57,6 +50,14 @@ lookup_parser.add_argument(
|
||||
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--offline-only',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
dest = 'offline_only',
|
||||
help = 'Attempt to searching with offline only mode'
|
||||
)
|
||||
|
||||
hash_parser = libc_commands.add_parser(
|
||||
'hash',
|
||||
help = 'Display information of a libc version given an unique hash',
|
||||
@@ -80,19 +81,12 @@ hash_parser.add_argument(
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--download-libc',
|
||||
'-d', '--download-libc',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
help = 'Attempt to download the matching libc.so'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--unstrip',
|
||||
action = 'store_true',
|
||||
default = True,
|
||||
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--no-unstrip',
|
||||
action = 'store_false',
|
||||
@@ -100,6 +94,14 @@ hash_parser.add_argument(
|
||||
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--offline-only',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
dest = 'offline_only',
|
||||
help = 'Attempt to searching with offline only mode'
|
||||
)
|
||||
|
||||
file_parser = libc_commands.add_parser(
|
||||
'file',
|
||||
help = 'Dump information about a libc binary',
|
||||
@@ -130,25 +132,34 @@ file_parser.add_argument(
|
||||
file_parser.add_argument(
|
||||
'--unstrip',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
dest = 'unstrip',
|
||||
help = 'Attempt to unstrip the libc binary inplace with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
fetch_parser = libc_commands.add_parser(
|
||||
'fetch',
|
||||
help = 'Fetch libc database',
|
||||
description = 'Fetch libc database. If no argument passed, it will init and upgrade libc-database repository',
|
||||
)
|
||||
|
||||
fetch_parser.add_argument(
|
||||
'path',
|
||||
nargs = '?',
|
||||
default = context.local_libcdb,
|
||||
help = 'Set libc-database path, If it is empty, the default path will be `context.local_libcdb` (%s)' % context.local_libcdb
|
||||
)
|
||||
|
||||
fetch_parser.add_argument(
|
||||
'-u', '--update',
|
||||
metavar = 'update',
|
||||
nargs = '+',
|
||||
choices = ['all', 'ubuntu', 'debian', 'rpm', 'centos', 'arch', 'alpine', 'kali', 'parrotsec', 'launchpad'],
|
||||
help = 'Fetch the desired libc categories'
|
||||
)
|
||||
|
||||
common_symbols = ['dup2', 'printf', 'puts', 'read', 'system', 'write']
|
||||
|
||||
def find_libc(params):
|
||||
import requests
|
||||
url = "https://libc.rip/api/find"
|
||||
result = requests.post(url, json=params, timeout=20)
|
||||
log.debug('Request: %s', params)
|
||||
log.debug('Result: %s', result.json())
|
||||
if result.status_code != 200 or len(result.json()) == 0:
|
||||
log.failure("Could not find libc for %s on libc.rip", params)
|
||||
return []
|
||||
|
||||
return result.json()
|
||||
|
||||
def print_libc(libc):
|
||||
def print_libc_info(libc):
|
||||
log.info('%s', text.red(libc['id']))
|
||||
log.indented('\t%-20s %s', text.green('BuildID:'), libc['buildid'])
|
||||
log.indented('\t%-20s %s', text.green('MD5:'), libc['md5'])
|
||||
@@ -158,14 +169,39 @@ def print_libc(libc):
|
||||
for symbol in libc['symbols'].items():
|
||||
log.indented('\t%25s = %s', symbol[0], symbol[1])
|
||||
|
||||
def handle_remote_libc(args, libc):
|
||||
print_libc(libc)
|
||||
if args.download_libc:
|
||||
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
|
||||
if path:
|
||||
if args.unstrip:
|
||||
libcdb.unstrip_libc(path)
|
||||
shutil.copy(path, './{}.so'.format(libc['id']))
|
||||
def print_libc_elf(exe):
|
||||
from hashlib import md5, sha1, sha256
|
||||
|
||||
log.info('%s', text.red(os.path.basename(exe.path)))
|
||||
|
||||
libc_version = get_libc_version(exe)
|
||||
if libc_version:
|
||||
log.indented('%-20s %s', text.green('Version:'), libc_version)
|
||||
|
||||
if exe.buildid:
|
||||
log.indented('%-20s %s', text.green('BuildID:'), enhex(exe.buildid))
|
||||
|
||||
log.indented('%-20s %s', text.green('MD5:'), md5(exe.data).hexdigest())
|
||||
log.indented('%-20s %s', text.green('SHA1:'), sha1(exe.data).hexdigest())
|
||||
log.indented('%-20s %s', text.green('SHA256:'), sha256(exe.data).hexdigest())
|
||||
|
||||
# Always dump the basic list of common symbols
|
||||
log.indented('%s', text.green('Symbols:'))
|
||||
synthetic_symbols = collect_synthetic_symbols(exe)
|
||||
|
||||
symbols = common_symbols + (args.symbols or []) + synthetic_symbols
|
||||
symbols.sort()
|
||||
for symbol in symbols:
|
||||
if symbol not in exe.symbols:
|
||||
log.indented('%25s = %s', symbol, text.red('not found'))
|
||||
else:
|
||||
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))
|
||||
|
||||
def get_libc_version(exe):
|
||||
res = re.search(br'libc[ -](\d+\.\d+)', exe.data)
|
||||
if res:
|
||||
return res.group(1).decode()
|
||||
return None
|
||||
|
||||
def translate_offset(offs, args, exe):
|
||||
if args.offset:
|
||||
@@ -176,8 +212,12 @@ def translate_offset(offs, args, exe):
|
||||
return offs
|
||||
|
||||
def collect_synthetic_symbols(exe):
|
||||
available_symbols = ['str_bin_sh']
|
||||
exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
|
||||
available_symbols = []
|
||||
try:
|
||||
exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
|
||||
available_symbols.append('str_bin_sh')
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
libc_start_main_return = exe.libc_start_main_return
|
||||
if libc_start_main_return > 0:
|
||||
@@ -196,52 +236,56 @@ def main(args):
|
||||
if len(pairs) % 2 != 0:
|
||||
log.failure('Uneven number of arguments. Please provide "symbol offset" pairs')
|
||||
return
|
||||
|
||||
|
||||
symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
|
||||
matched_libcs = find_libc({'symbols': symbols})
|
||||
matched_libcs = libcdb.search_by_symbol_offsets(symbols, offline_only=args.offline_only, return_raw=True)
|
||||
|
||||
for libc in matched_libcs:
|
||||
handle_remote_libc(args, libc)
|
||||
print_libc_info(libc)
|
||||
if args.download_libc:
|
||||
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
|
||||
if path:
|
||||
shutil.copy(path, './{}.so'.format(libc['id']))
|
||||
|
||||
elif args.libc_command == 'hash':
|
||||
inverted_map = {v: k for k, v in libcdb.MAP_TYPES.items()}
|
||||
hash_type = inverted_map.get(args.hash_type, args.hash_type)
|
||||
|
||||
for hash_value in args.hash_value:
|
||||
matched_libcs = find_libc({args.hash_type: hash_value})
|
||||
for libc in matched_libcs:
|
||||
handle_remote_libc(args, libc)
|
||||
path = libcdb.search_by_hash(hash_value, hash_type, unstrip=args.unstrip, offline_only=args.offline_only)
|
||||
exe = ELF(path, checksec=False)
|
||||
print_libc_elf(exe)
|
||||
|
||||
if args.download_libc:
|
||||
# if we cannot get actual libc version then copy with cache name
|
||||
shutil.copy(path, './libc-{}.so'.format(get_libc_version(exe) or Path(path).stem))
|
||||
|
||||
elif args.libc_command == 'file':
|
||||
from hashlib import md5, sha1, sha256
|
||||
for file in args.files:
|
||||
if not os.path.exists(file) or not os.path.isfile(file):
|
||||
log.failure('File does not exist %s', args.file)
|
||||
continue
|
||||
|
||||
|
||||
if args.unstrip:
|
||||
libcdb.unstrip_libc(file)
|
||||
|
||||
exe = ELF(file, checksec=False)
|
||||
log.info('%s', text.red(os.path.basename(file)))
|
||||
print_libc_elf(ELF(file, checksec=False))
|
||||
|
||||
libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data)
|
||||
if libc_version:
|
||||
log.indented('%-20s %s', text.green('Version:'), libc_version.group(1).decode())
|
||||
elif args.libc_command == 'fetch':
|
||||
|
||||
if exe.buildid:
|
||||
log.indented('%-20s %s', text.green('BuildID:'), enhex(exe.buildid))
|
||||
log.indented('%-20s %s', text.green('MD5:'), md5(exe.data).hexdigest())
|
||||
log.indented('%-20s %s', text.green('SHA1:'), sha1(exe.data).hexdigest())
|
||||
log.indented('%-20s %s', text.green('SHA256:'), sha256(exe.data).hexdigest())
|
||||
if args.update:
|
||||
subprocess.check_call(['./get'] + args.update, cwd=args.path)
|
||||
|
||||
# Always dump the basic list of common symbols
|
||||
log.indented('%s', text.green('Symbols:'))
|
||||
synthetic_symbols = collect_synthetic_symbols(exe)
|
||||
else:
|
||||
if not Path(args.path).exists():
|
||||
if yesno("Would you like to initialize the libc-database repository? "
|
||||
"If the path already exists, this prompt will not display, and automatically upgrade repository."):
|
||||
log.waitfor("init libc-database repository")
|
||||
subprocess.check_call(['git', 'clone', 'https://github.com/niklasb/libc-database/', args.path])
|
||||
else:
|
||||
log.waitfor("upgrade libc-database repository")
|
||||
subprocess.check_call(['git', 'pull'], cwd=args.path)
|
||||
|
||||
symbols = common_symbols + (args.symbols or []) + synthetic_symbols
|
||||
symbols.sort()
|
||||
for symbol in symbols:
|
||||
if symbol not in exe.symbols:
|
||||
log.indented('%25s = %s', symbol, text.red('not found'))
|
||||
else:
|
||||
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
from pwnlib.commandline import asm
|
||||
from pwnlib.commandline import checksec
|
||||
from pwnlib.commandline import common
|
||||
@@ -23,8 +21,7 @@ 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
|
||||
from pwnlib.commandline.common import parser as parser
|
||||
|
||||
commands = {
|
||||
'asm': asm.main,
|
||||
@@ -50,12 +47,7 @@ commands = {
|
||||
}
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_usage()
|
||||
sys.exit()
|
||||
args = parser.parse_args()
|
||||
with context.local(log_console = sys.stderr):
|
||||
commands[args.command](args)
|
||||
common.entrypoint(commands)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -110,4 +109,4 @@ def main(args):
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -53,4 +53,4 @@ def main(args):
|
||||
args.output.write(result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -110,4 +110,4 @@ def main(args):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -72,17 +71,9 @@ p.add_argument(
|
||||
|
||||
p.add_argument(
|
||||
'shellcode',
|
||||
nargs = '?',
|
||||
help = 'The shellcode you want',
|
||||
type = str
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
'args',
|
||||
nargs = '*',
|
||||
metavar = 'arg',
|
||||
default = (),
|
||||
help = 'Argument to the chosen shellcode',
|
||||
help = 'The shellcodes you want. shellcode [args ...] [+ shellcode [args ...]]',
|
||||
type = str
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
@@ -92,6 +83,12 @@ p.add_argument(
|
||||
action='store_true'
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
'--delim',
|
||||
help='Set the delimiter between multilple shellcodes',
|
||||
default='+'
|
||||
)
|
||||
|
||||
p.add_argument(
|
||||
'-b',
|
||||
'--before',
|
||||
@@ -173,24 +170,50 @@ p.add_argument(
|
||||
help='Generated ELF is a shared library'
|
||||
)
|
||||
|
||||
def get_template(name):
|
||||
func = shellcraft
|
||||
for attr in name.split('.'):
|
||||
func = getattr(func, attr)
|
||||
return func
|
||||
def get_template(shellcodes):
|
||||
funcs = []
|
||||
for shellcode in shellcodes:
|
||||
func = shellcraft
|
||||
cur_name = shellcode[0]
|
||||
args = []
|
||||
if len(shellcode) > 1:
|
||||
args = shellcode[1:]
|
||||
for attr in cur_name.split('.'):
|
||||
func = getattr(func, attr)
|
||||
funcs.append((cur_name, func, args))
|
||||
return funcs
|
||||
|
||||
def is_not_a_syscall_template(name):
|
||||
template_src = shellcraft._get_source(name)
|
||||
return '/syscalls' not in template_src
|
||||
|
||||
def main(args):
|
||||
delim = '+'
|
||||
if args.delim:
|
||||
delim = args.delim.strip()
|
||||
|
||||
shellcodes = []
|
||||
if args.shellcode:
|
||||
current = []
|
||||
for s in args.shellcode:
|
||||
if s.strip() == delim:
|
||||
shellcodes.append(current)
|
||||
current = []
|
||||
else:
|
||||
current.append(s)
|
||||
if len(current) > 0:
|
||||
shellcodes.append(current)
|
||||
|
||||
if args.list:
|
||||
templates = shellcraft.templates
|
||||
|
||||
if args.shellcode:
|
||||
templates = filter(lambda a: args.shellcode in a, templates)
|
||||
template_array = []
|
||||
for s in shellcodes:
|
||||
template_array.extend(list(filter(lambda a: s[0] in a, templates)))
|
||||
templates = template_array
|
||||
elif not args.syscalls:
|
||||
templates = filter(is_not_a_syscall_template, templates)
|
||||
templates = list(filter(is_not_a_syscall_template, templates))
|
||||
|
||||
print('\n'.join(templates))
|
||||
exit()
|
||||
@@ -200,84 +223,92 @@ def main(args):
|
||||
exit()
|
||||
|
||||
try:
|
||||
func = get_template(args.shellcode)
|
||||
funcs = get_template(shellcodes)
|
||||
except AttributeError:
|
||||
log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
|
||||
|
||||
if args.show:
|
||||
# remove doctests
|
||||
doc = []
|
||||
in_doctest = False
|
||||
block_indent = None
|
||||
caption = None
|
||||
lines = func.__doc__.splitlines()
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
if line.lstrip().startswith('>>>'):
|
||||
# this line starts a doctest
|
||||
in_doctest = True
|
||||
block_indent = None
|
||||
if caption:
|
||||
# delete back up to the caption
|
||||
doc = doc[:caption - i]
|
||||
caption = None
|
||||
elif line == '':
|
||||
# skip blank lines
|
||||
pass
|
||||
elif in_doctest:
|
||||
# indentation marks the end of a doctest
|
||||
indent = len(line) - len(line.lstrip())
|
||||
if block_indent is None:
|
||||
if not line.lstrip().startswith('...'):
|
||||
block_indent = indent
|
||||
elif indent < block_indent:
|
||||
in_doctest = False
|
||||
for (name, func, _args) in funcs:
|
||||
# remove doctests
|
||||
doc = []
|
||||
in_doctest = False
|
||||
block_indent = None
|
||||
caption = None
|
||||
lines = func.__doc__.splitlines()
|
||||
i = 0
|
||||
if len(funcs) > 1:
|
||||
print('%s:' % name)
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
if line.lstrip().startswith('>>>'):
|
||||
# this line starts a doctest
|
||||
in_doctest = True
|
||||
block_indent = None
|
||||
# re-evalutate this line
|
||||
continue
|
||||
elif line.endswith(':'):
|
||||
# save index of caption
|
||||
caption = i
|
||||
else:
|
||||
# this is not blank space and we're not in a doctest, so the
|
||||
# previous caption (if any) was not for a doctest
|
||||
caption = None
|
||||
if caption:
|
||||
# delete back up to the caption
|
||||
doc = doc[:caption - i]
|
||||
caption = None
|
||||
elif line == '':
|
||||
# skip blank lines
|
||||
pass
|
||||
elif in_doctest:
|
||||
# indentation marks the end of a doctest
|
||||
indent = len(line) - len(line.lstrip())
|
||||
if block_indent is None:
|
||||
if not line.lstrip().startswith('...'):
|
||||
block_indent = indent
|
||||
elif indent < block_indent:
|
||||
in_doctest = False
|
||||
block_indent = None
|
||||
# re-evalutate this line
|
||||
continue
|
||||
elif line.endswith(':'):
|
||||
# save index of caption
|
||||
caption = i
|
||||
else:
|
||||
# this is not blank space and we're not in a doctest, so the
|
||||
# previous caption (if any) was not for a doctest
|
||||
caption = None
|
||||
|
||||
if not in_doctest:
|
||||
doc.append(line)
|
||||
i += 1
|
||||
print('\n'.join(doc).rstrip())
|
||||
if not in_doctest:
|
||||
doc.append(line)
|
||||
i += 1
|
||||
print('\n'.join(doc).rstrip())
|
||||
if len(funcs) > 1:
|
||||
print('')
|
||||
exit()
|
||||
|
||||
defargs = len(six.get_function_defaults(func) or ())
|
||||
reqargs = six.get_function_code(func).co_argcount - defargs
|
||||
if len(args.args) < reqargs:
|
||||
if defargs > 0:
|
||||
log.critical('%s takes at least %d arguments' % (args.shellcode, reqargs))
|
||||
sys.exit(1)
|
||||
else:
|
||||
log.critical('%s takes exactly %d arguments' % (args.shellcode, reqargs))
|
||||
sys.exit(1)
|
||||
code_array = []
|
||||
for (name, func, func_args) in funcs:
|
||||
defargs = len(six.get_function_defaults(func) or ())
|
||||
reqargs = six.get_function_code(func).co_argcount - defargs
|
||||
|
||||
# Captain uglyness saves the day!
|
||||
for i, val in enumerate(args.args):
|
||||
try:
|
||||
args.args[i] = util.safeeval.expr(val)
|
||||
except ValueError:
|
||||
pass
|
||||
if len(func_args) < reqargs:
|
||||
if defargs > 0:
|
||||
log.critical('%s takes at least %d arguments' % (name, reqargs))
|
||||
sys.exit(1)
|
||||
else:
|
||||
log.critical('%s takes exactly %d arguments' % (name, reqargs))
|
||||
sys.exit(1)
|
||||
|
||||
# And he strikes again!
|
||||
list(map(common.context_arg, args.shellcode.split('.')))
|
||||
code = func(*args.args)
|
||||
# Captain uglyness saves the day!
|
||||
for i, val in enumerate(func_args):
|
||||
try:
|
||||
func_args[i] = util.safeeval.expr(val)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# And he strikes again!
|
||||
list(map(common.context_arg, name.split('.')))
|
||||
code_array.append(func(*func_args))
|
||||
|
||||
code = "".join(code_array)
|
||||
|
||||
if args.before:
|
||||
code = shellcraft.trap() + code
|
||||
if args.after:
|
||||
code = code + shellcraft.trap()
|
||||
|
||||
|
||||
if args.format in ['a', 'asm', 'assembly']:
|
||||
if args.color:
|
||||
from pygments import highlight
|
||||
@@ -320,7 +351,7 @@ def main(args):
|
||||
else:
|
||||
args.format = 'raw'
|
||||
|
||||
arch = args.shellcode.split('.')[0]
|
||||
arch = name.split('.')[0]
|
||||
|
||||
if args.debug:
|
||||
if not args.avoid:
|
||||
@@ -353,4 +384,4 @@ def main(args):
|
||||
args.out.write(code)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
Executable → Regular
+177
-16
@@ -1,30 +1,183 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from pwn import *
|
||||
from pwnlib.commandline import common
|
||||
from pwnlib.util.misc import which, parse_ldd_output, write
|
||||
|
||||
from mako.lookup import TemplateLookup
|
||||
from sys import stderr
|
||||
from mako.lookup import TemplateLookup, Template
|
||||
|
||||
parser = common.parser_commands.add_parser(
|
||||
'template',
|
||||
help = 'Generate an exploit template',
|
||||
description = 'Generate an exploit template'
|
||||
description = 'Generate an exploit template. If no arguments are given, '
|
||||
'the current directory is searched for an executable binary and '
|
||||
'libc. If only one binary is found, it is assumed to be the '
|
||||
'challenge binary.'
|
||||
)
|
||||
|
||||
parser.add_argument('exe', nargs='?', help='Target binary')
|
||||
# change path to hardcoded one when building the documentation
|
||||
printable_data_path = "pwnlib/data" if 'sphinx' in sys.modules else pwnlib.data.path
|
||||
|
||||
parser.add_argument('exe', nargs='?', help='Target binary. If not given, the current directory is searched for an executable 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', '--password', help='SSH Password', dest='password')
|
||||
parser.add_argument('--libc', help='Path to libc binary to use. If not given, the current directory is searched for a libc binary.')
|
||||
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')
|
||||
parser.add_argument('--template', help='Path to a custom template. Tries to use \'~/.config/pwntools/templates/pwnup.mako\', if it exists. '
|
||||
'Check \'%s\' for the default template shipped with pwntools.' %
|
||||
os.path.join(printable_data_path, "templates", "pwnup.mako"))
|
||||
parser.add_argument('--no-auto', help='Do not automatically detect missing binaries', action='store_false', dest='auto')
|
||||
|
||||
def get_docker_image_libraries():
|
||||
"""Tries to retrieve challenge libraries from a Docker image built from the Dockerfile in the current working directory.
|
||||
|
||||
The libraries are retrieved by parsing the output of running ldd on /bin/sh.
|
||||
Supports regular Docker images as well as jail images.
|
||||
"""
|
||||
with log.progress("Extracting challenge libraries from Docker image") as progress:
|
||||
if not which("docker"):
|
||||
progress.failure("docker command not found")
|
||||
return None, None
|
||||
# maps jail image name to the root directory of the child image
|
||||
jail_image_to_chroot_dir = {
|
||||
"pwn.red/jail": "/srv",
|
||||
}
|
||||
dockerfile = open("Dockerfile", "r").read()
|
||||
jail = None
|
||||
chroot_dir = "/"
|
||||
for jail_image in jail_image_to_chroot_dir:
|
||||
if re.search(r"^FROM %s" % jail_image, dockerfile, re.MULTILINE):
|
||||
jail = jail_image
|
||||
chroot_dir = jail_image_to_chroot_dir[jail_image]
|
||||
break
|
||||
try:
|
||||
progress.status("Building image")
|
||||
image_sha = subprocess.check_output(["docker", "build", "-q", "."], stderr=subprocess.PIPE, shell=False).decode().strip()
|
||||
|
||||
progress.status("Retrieving library paths")
|
||||
ldd_command = ["-c", "chroot %s /bin/sh -c 'ldd /bin/sh'" % chroot_dir]
|
||||
ldd_output = subprocess.check_output([
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"--entrypoint",
|
||||
"/bin/sh",
|
||||
] + (["--privileged"] if jail else []) + [
|
||||
image_sha,
|
||||
] + ldd_command,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False
|
||||
).decode()
|
||||
|
||||
libc, ld = None, None
|
||||
libc_basename, ld_basename = None, None
|
||||
for lib_path in parse_ldd_output(ldd_output):
|
||||
if "libc." in lib_path:
|
||||
libc = lib_path
|
||||
libc_basename = os.path.basename(lib_path)
|
||||
if "ld-" in lib_path:
|
||||
ld = lib_path
|
||||
ld_basename = os.path.basename(lib_path)
|
||||
|
||||
if not (libc and ld):
|
||||
progress.failure("Could not find libraries")
|
||||
return None, None
|
||||
|
||||
progress.status("Copying libraries to current directory")
|
||||
for filename, basename in zip((libc, ld), (libc_basename, ld_basename)):
|
||||
cat_command = ["-c", "chroot %s /bin/sh -c '/bin/cat %s'" % (chroot_dir, filename)]
|
||||
contents = subprocess.check_output([
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"--entrypoint",
|
||||
"/bin/sh",
|
||||
] + (["--privileged"] if jail else []) + [
|
||||
image_sha
|
||||
] + cat_command,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False
|
||||
)
|
||||
write(basename, contents)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.stderr.decode())
|
||||
log.error("docker failed with status: %d" % e.returncode)
|
||||
|
||||
progress.success("Retrieved libraries from Docker image")
|
||||
return libc_basename, ld_basename
|
||||
|
||||
def detect_missing_binaries(args):
|
||||
"""Automatically detects challenge binaries and libraries in the current directory.
|
||||
|
||||
This function scans the current directory for executable files, libc, and ld libraries.
|
||||
If a Dockerfile is present and no libraries are found, it attempts to extract them from
|
||||
the Docker image, but only if the binary is not statically linked.
|
||||
|
||||
Args:
|
||||
args: Argument namespace containing exe and libc attributes.
|
||||
|
||||
Returns:
|
||||
tuple: A pair of (executable_path, libc_path) where either may be None if not found.
|
||||
"""
|
||||
log.info("Automatically detecting challenge binaries...")
|
||||
# look for challenge binary, libc, and ld in current directory
|
||||
exe, libc, ld = args.exe, args.libc, None
|
||||
has_dockerfile = False
|
||||
other_files = []
|
||||
for filename in os.listdir("."):
|
||||
if not os.path.isfile(filename):
|
||||
continue
|
||||
if not libc and ('libc-' in filename or 'libc.' in filename):
|
||||
libc = filename
|
||||
elif not ld and 'ld-' in filename:
|
||||
ld = filename
|
||||
elif filename == "Dockerfile":
|
||||
has_dockerfile = True
|
||||
else:
|
||||
if os.access(filename, os.X_OK):
|
||||
other_files.append(filename)
|
||||
if not exe:
|
||||
if len(other_files) == 1:
|
||||
exe = other_files[0]
|
||||
elif len(other_files) > 1:
|
||||
log.warning("Failed to find challenge binary. There are multiple binaries in the current directory: %s", other_files)
|
||||
|
||||
# Check if the binary is statically linked before trying to extract libraries from Docker
|
||||
is_statically_linked = False
|
||||
if exe:
|
||||
try:
|
||||
binary = ELF(exe, checksec=False)
|
||||
is_statically_linked = binary.statically_linked
|
||||
if is_statically_linked:
|
||||
log.info("Binary is statically linked, no need for external libraries")
|
||||
except Exception as e:
|
||||
log.warning("Could not check if binary is statically linked: %s", e)
|
||||
|
||||
# Only extract libraries from Docker if the binary is not statically linked
|
||||
if has_dockerfile and exe and not (libc or ld) and not is_statically_linked:
|
||||
libc, ld = get_docker_image_libraries()
|
||||
|
||||
if exe != args.exe:
|
||||
log.success("Found challenge binary %r", exe)
|
||||
if libc != args.libc:
|
||||
log.success("Found libc binary %r", libc)
|
||||
return exe, libc
|
||||
|
||||
def main(args):
|
||||
|
||||
lookup = TemplateLookup(
|
||||
directories = [os.path.join(pwnlib.data.path, 'templates')],
|
||||
directories = [
|
||||
os.path.expanduser('~/.config/pwntools/templates/'),
|
||||
os.path.join(pwnlib.data.path, 'templates')
|
||||
],
|
||||
module_directory = None
|
||||
)
|
||||
|
||||
@@ -34,25 +187,32 @@ def main(args):
|
||||
if not (args.path or args.exe):
|
||||
log.error("Must specify --path or a exe")
|
||||
|
||||
s = ssh(args.user, args.host, args.port or 22, args.password or None)
|
||||
|
||||
try:
|
||||
remote_file = args.path or args.exe
|
||||
s.download(remote_file)
|
||||
except Exception:
|
||||
log.warning("Could not download file %r, opening a shell", remote_file)
|
||||
s.interactive()
|
||||
return
|
||||
with ssh(args.user, args.host, args.port or 22, args.password or None) as s:
|
||||
try:
|
||||
remote_file = args.path or args.exe
|
||||
s.download(remote_file)
|
||||
except Exception:
|
||||
log.warning("Could not download file %r, opening a shell", remote_file)
|
||||
s.interactive()
|
||||
return
|
||||
|
||||
if not args.exe:
|
||||
args.exe = os.path.basename(args.path)
|
||||
|
||||
template = lookup.get_template('pwnup.mako')
|
||||
if args.auto and (args.exe is None or args.libc is None):
|
||||
args.exe, args.libc = detect_missing_binaries(args)
|
||||
|
||||
if args.template:
|
||||
template = Template(filename=args.template) # Failing on invalid file is ok
|
||||
else:
|
||||
template = lookup.get_template('pwnup.mako')
|
||||
|
||||
output = template.render(args.exe,
|
||||
args.host,
|
||||
args.port,
|
||||
args.user,
|
||||
args.password,
|
||||
args.libc,
|
||||
args.path,
|
||||
args.quiet)
|
||||
|
||||
@@ -74,4 +234,5 @@ def main(args):
|
||||
except OSError: pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python2
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -31,4 +30,4 @@ def main(args):
|
||||
raise
|
||||
|
||||
if __name__ == '__main__':
|
||||
common.main(__file__)
|
||||
common.main(__file__, main)
|
||||
@@ -30,4 +30,4 @@ def main(a):
|
||||
subprocess.check_call(result, shell=False)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
@@ -29,4 +29,4 @@ def main(a):
|
||||
log.info("Pwntools v%s" % version)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
pwnlib.commandline.common.main(__file__, main)
|
||||
Whitespace-only changes.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+153
-18
@@ -302,7 +302,7 @@ class ContextType(object):
|
||||
>>> context.os == 'linux'
|
||||
True
|
||||
>>> context.arch = 'arm'
|
||||
>>> vars(context) == {'arch': 'arm', 'bits': 32, 'endian': 'little', 'os': 'linux'}
|
||||
>>> vars(context) == {'arch': 'arm', 'bits': 32, 'endian': 'little', 'os': 'linux', 'newline': b'\n'}
|
||||
True
|
||||
>>> context.endian
|
||||
'little'
|
||||
@@ -359,13 +359,16 @@ class ContextType(object):
|
||||
'encoding': 'auto',
|
||||
'endian': 'little',
|
||||
'gdbinit': "",
|
||||
'gdb_binary': "",
|
||||
'kernel': None,
|
||||
'local_libcdb': "/var/lib/libc-database",
|
||||
'log_level': logging.INFO,
|
||||
'log_file': _devnull(),
|
||||
'log_console': sys.stdout,
|
||||
'randomize': False,
|
||||
'rename_corefiles': True,
|
||||
'newline': b'\n',
|
||||
'throw_eof_on_incomplete_line': None,
|
||||
'noptrace': False,
|
||||
'os': 'linux',
|
||||
'proxy': None,
|
||||
@@ -375,8 +378,19 @@ class ContextType(object):
|
||||
'timeout': Timeout.maximum,
|
||||
}
|
||||
|
||||
#: Valid values for :meth:`pwnlib.context.ContextType.os`
|
||||
oses = sorted(('linux','freebsd','windows','cgc','android','baremetal'))
|
||||
unix_like = {'newline': b'\n'}
|
||||
windows_like = {'newline': b'\r\n'}
|
||||
|
||||
#: Keys are valid values for :meth:`pwnlib.context.ContextType.os`
|
||||
oses = _longest({
|
||||
'linux': unix_like,
|
||||
'freebsd': unix_like,
|
||||
'windows': windows_like,
|
||||
'cgc': unix_like,
|
||||
'android': unix_like,
|
||||
'baremetal': unix_like,
|
||||
'darwin': unix_like,
|
||||
})
|
||||
|
||||
big_32 = {'endian': 'big', 'bits': 32}
|
||||
big_64 = {'endian': 'big', 'bits': 64}
|
||||
@@ -445,14 +459,14 @@ class ContextType(object):
|
||||
|
||||
|
||||
def copy(self):
|
||||
"""copy() -> dict
|
||||
r"""copy() -> dict
|
||||
Returns a copy of the current context as a dictionary.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> context.clear()
|
||||
>>> context.os = 'linux'
|
||||
>>> vars(context) == {'os': 'linux'}
|
||||
>>> vars(context) == {'os': 'linux', 'newline': b'\n'}
|
||||
True
|
||||
"""
|
||||
return self._tls.copy()
|
||||
@@ -581,9 +595,9 @@ class ContextType(object):
|
||||
... log.debug("DEBUG")
|
||||
... log.info("INFO")
|
||||
... log.warn("WARN")
|
||||
[DEBUG] DEBUG
|
||||
[*] INFO
|
||||
[!] WARN
|
||||
[...] DEBUG
|
||||
[...] INFO
|
||||
[...] WARN
|
||||
"""
|
||||
level = 'error'
|
||||
if context.log_level <= logging.DEBUG:
|
||||
@@ -651,7 +665,7 @@ class ContextType(object):
|
||||
information is printed.
|
||||
|
||||
>>> with context.verbose: func()
|
||||
[DEBUG] Hello
|
||||
[...] Hello
|
||||
|
||||
"""
|
||||
return self.local(log_level='debug')
|
||||
@@ -787,7 +801,7 @@ class ContextType(object):
|
||||
try:
|
||||
defaults = self.architectures[arch]
|
||||
except KeyError:
|
||||
raise AttributeError('AttributeError: arch must be one of %r' % sorted(self.architectures))
|
||||
raise AttributeError('AttributeError: arch (%r) must be one of %r' % (arch, sorted(self.architectures)))
|
||||
|
||||
for k,v in defaults.items():
|
||||
if k not in self._tls:
|
||||
@@ -830,6 +844,7 @@ class ContextType(object):
|
||||
The default value is ``32``, but changes according to :attr:`arch`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> context.clear()
|
||||
>>> context.bits == 32
|
||||
True
|
||||
@@ -1020,6 +1035,8 @@ class ContextType(object):
|
||||
"""
|
||||
if isinstance(value, (bytes, six.text_type)):
|
||||
# check if mode was specified as "[value],[mode]"
|
||||
from pwnlib.util.packing import _need_text
|
||||
value = _need_text(value)
|
||||
if ',' not in value:
|
||||
value += ',a'
|
||||
filename, mode = value.rsplit(',', 1)
|
||||
@@ -1070,31 +1087,105 @@ class ContextType(object):
|
||||
stream = open(stream, 'wt')
|
||||
return stream
|
||||
|
||||
@_validator
|
||||
def local_libcdb(self, path):
|
||||
"""
|
||||
Sets path to local libc-database, get more information for libc-database:
|
||||
https://github.com/niklasb/libc-database
|
||||
|
||||
Works in :attr:`pwnlib.libcdb` when searching by local database provider.
|
||||
|
||||
The default value is ``/var/lib/libc-database``.
|
||||
|
||||
Sets `context.local_libcdb` to empty string or `None` will turn off local libc-database integration.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> context.local_libcdb = pwnlib.data.elf.path
|
||||
>>> context.local_libcdb = 'foobar'
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'foobar' does not exist, please download libc-database first
|
||||
"""
|
||||
|
||||
if not os.path.isdir(path):
|
||||
raise AttributeError("'%s' does not exist, please download libc-database first" % path)
|
||||
|
||||
return path
|
||||
|
||||
@property
|
||||
def mask(self):
|
||||
return (1 << self.bits) - 1
|
||||
|
||||
@_validator
|
||||
def os(self, os):
|
||||
"""
|
||||
r"""
|
||||
Operating system of the target machine.
|
||||
|
||||
The default value is ``linux``.
|
||||
|
||||
Allowed values are listed in :attr:`pwnlib.context.ContextType.oses`.
|
||||
|
||||
Side Effects:
|
||||
|
||||
If an os is specified some attributes will be set on the context
|
||||
if a user has not already set a value.
|
||||
|
||||
The following property may be modified:
|
||||
|
||||
- :attr:`newline`
|
||||
|
||||
Raises:
|
||||
AttributeError: An invalid os was specified
|
||||
|
||||
Examples:
|
||||
|
||||
>>> context.os = 'linux'
|
||||
>>> context.clear()
|
||||
>>> context.os == 'linux' # Default os
|
||||
True
|
||||
|
||||
>>> context.os = 'freebsd'
|
||||
>>> context.os == 'freebsd'
|
||||
True
|
||||
|
||||
>>> context.os = 'foobar' #doctest: +ELLIPSIS
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: os must be one of ['android', 'baremetal', 'cgc', 'freebsd', 'linux', 'windows']
|
||||
|
||||
>>> context.clear()
|
||||
>>> context.newline == b'\n' # Default value
|
||||
True
|
||||
>>> context.os = 'windows'
|
||||
>>> context.newline == b'\r\n' # New value
|
||||
True
|
||||
|
||||
Note that expressly setting :attr:`newline` means that we use
|
||||
that value instead of the default
|
||||
|
||||
>>> context.clear()
|
||||
>>> context.newline = b'\n'
|
||||
>>> context.os = 'windows'
|
||||
>>> context.newline == b'\n'
|
||||
True
|
||||
|
||||
Setting the os can override the default for :attr:`newline`
|
||||
|
||||
>>> context.clear()
|
||||
>>> context.os = 'windows'
|
||||
>>> vars(context) == {'os': 'windows', 'newline': b'\r\n'}
|
||||
True
|
||||
"""
|
||||
os = os.lower()
|
||||
|
||||
if os not in self.oses:
|
||||
raise AttributeError("os must be one of %r" % self.oses)
|
||||
try:
|
||||
defaults = self.oses[os]
|
||||
except KeyError:
|
||||
raise AttributeError("os must be one of %r" % sorted(self.oses))
|
||||
|
||||
for k,v in defaults.items():
|
||||
if k not in self._tls:
|
||||
self._tls[k] = v
|
||||
|
||||
return os
|
||||
|
||||
@@ -1287,9 +1378,9 @@ class ContextType(object):
|
||||
def cache_dir_base(self, new_base):
|
||||
"""Base directory to use for caching content.
|
||||
|
||||
Changing this to a different value will clear the `cache_dir` path
|
||||
Changing this to a different value will clear the :attr:`cache_dir` path
|
||||
stored in TLS since a new path will need to be generated to respect the
|
||||
new `cache_dir_base` value.
|
||||
new :attr:`cache_dir_base` value.
|
||||
"""
|
||||
|
||||
if new_base != self.cache_dir_base:
|
||||
@@ -1304,6 +1395,9 @@ class ContextType(object):
|
||||
|
||||
Note:
|
||||
May be either a path string, or :const:`None`.
|
||||
Set to :const:`None` to disable caching.
|
||||
Set to :const:`True` to generate the default cache directory path
|
||||
based on :attr:`cache_dir_base` again.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1311,12 +1405,18 @@ class ContextType(object):
|
||||
>>> cache_dir is not None
|
||||
True
|
||||
>>> os.chmod(cache_dir, 0o000)
|
||||
>>> del context._tls['cache_dir']
|
||||
>>> context.cache_dir = True
|
||||
>>> context.cache_dir is None
|
||||
True
|
||||
>>> os.chmod(cache_dir, 0o755)
|
||||
>>> cache_dir == context.cache_dir
|
||||
True
|
||||
>>> context.cache_dir = None
|
||||
>>> context.cache_dir is None
|
||||
True
|
||||
>>> context.cache_dir = True
|
||||
>>> context.cache_dir is not None
|
||||
True
|
||||
"""
|
||||
try:
|
||||
# If the TLS already has a cache directory path, we return it
|
||||
@@ -1361,7 +1461,9 @@ class ContextType(object):
|
||||
|
||||
@cache_dir.setter
|
||||
def cache_dir(self, v):
|
||||
if os.access(v, os.W_OK):
|
||||
if v is True:
|
||||
del self._tls["cache_dir"]
|
||||
elif v is None or os.access(v, os.W_OK):
|
||||
# Stash this in TLS for later reuse
|
||||
self._tls["cache_dir"] = v
|
||||
|
||||
@@ -1401,6 +1503,25 @@ class ContextType(object):
|
||||
# circular imports
|
||||
from pwnlib.util.packing import _need_bytes
|
||||
return _need_bytes(v)
|
||||
|
||||
@_validator
|
||||
def throw_eof_on_incomplete_line(self, v):
|
||||
"""Whether to raise an :class:`EOFError` if an EOF is received before a newline in ``tube.recvline``.
|
||||
|
||||
Controls if an :class:`EOFError` is treated as newline in ``tube.recvline`` and similar functions
|
||||
and whether a warning should be logged about it.
|
||||
|
||||
Possible values are:
|
||||
|
||||
- ``True``: Raise an :class:`EOFError` if an EOF is received before a newline.
|
||||
- ``False``: Return the data received so far if an EOF is received
|
||||
before a newline without logging a warning.
|
||||
- ``None``: Return the data received so far if an EOF is received
|
||||
before a newline and log a warning.
|
||||
|
||||
Default value is ``None``.
|
||||
"""
|
||||
return v if v is None else bool(v)
|
||||
|
||||
|
||||
@_validator
|
||||
@@ -1419,6 +1540,20 @@ class ContextType(object):
|
||||
"""
|
||||
return str(value)
|
||||
|
||||
@_validator
|
||||
def gdb_binary(self, value):
|
||||
"""Path to the binary that is used when running GDB locally.
|
||||
|
||||
This is useful when you have multiple versions of gdb installed or the gdb binary is
|
||||
called something different.
|
||||
|
||||
If set to an empty string, pwntools will try to search for a reasonable gdb binary from
|
||||
the path.
|
||||
|
||||
Default value is ``""``.
|
||||
"""
|
||||
return str(value)
|
||||
|
||||
@_validator
|
||||
def cyclic_alphabet(self, alphabet):
|
||||
"""Cyclic alphabet.
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,304 @@
|
||||
#define __NR_io_setup 0
|
||||
#define __NR_io_destroy 1
|
||||
#define __NR_io_submit 2
|
||||
#define __NR_io_cancel 3
|
||||
#define __NR_io_getevents 4
|
||||
#define __NR_setxattr 5
|
||||
#define __NR_lsetxattr 6
|
||||
#define __NR_fsetxattr 7
|
||||
#define __NR_getxattr 8
|
||||
#define __NR_lgetxattr 9
|
||||
#define __NR_fgetxattr 10
|
||||
#define __NR_listxattr 11
|
||||
#define __NR_llistxattr 12
|
||||
#define __NR_flistxattr 13
|
||||
#define __NR_removexattr 14
|
||||
#define __NR_lremovexattr 15
|
||||
#define __NR_fremovexattr 16
|
||||
#define __NR_getcwd 17
|
||||
#define __NR_lookup_dcookie 18
|
||||
#define __NR_eventfd2 19
|
||||
#define __NR_epoll_create1 20
|
||||
#define __NR_epoll_ctl 21
|
||||
#define __NR_epoll_pwait 22
|
||||
#define __NR_dup 23
|
||||
#define __NR_dup3 24
|
||||
#define __NR_fcntl 25
|
||||
#define __NR_inotify_init1 26
|
||||
#define __NR_inotify_add_watch 27
|
||||
#define __NR_inotify_rm_watch 28
|
||||
#define __NR_ioctl 29
|
||||
#define __NR_ioprio_set 30
|
||||
#define __NR_ioprio_get 31
|
||||
#define __NR_flock 32
|
||||
#define __NR_mknodat 33
|
||||
#define __NR_mkdirat 34
|
||||
#define __NR_unlinkat 35
|
||||
#define __NR_symlinkat 36
|
||||
#define __NR_linkat 37
|
||||
#define __NR_umount2 39
|
||||
#define __NR_mount 40
|
||||
#define __NR_pivot_root 41
|
||||
#define __NR_nfsservctl 42
|
||||
#define __NR_statfs 43
|
||||
#define __NR_fstatfs 44
|
||||
#define __NR_truncate 45
|
||||
#define __NR_ftruncate 46
|
||||
#define __NR_fallocate 47
|
||||
#define __NR_faccessat 48
|
||||
#define __NR_chdir 49
|
||||
#define __NR_fchdir 50
|
||||
#define __NR_chroot 51
|
||||
#define __NR_fchmod 52
|
||||
#define __NR_fchmodat 53
|
||||
#define __NR_fchownat 54
|
||||
#define __NR_fchown 55
|
||||
#define __NR_openat 56
|
||||
#define __NR_close 57
|
||||
#define __NR_vhangup 58
|
||||
#define __NR_pipe2 59
|
||||
#define __NR_quotactl 60
|
||||
#define __NR_getdents64 61
|
||||
#define __NR_lseek 62
|
||||
#define __NR_read 63
|
||||
#define __NR_write 64
|
||||
#define __NR_readv 65
|
||||
#define __NR_writev 66
|
||||
#define __NR_pread64 67
|
||||
#define __NR_pwrite64 68
|
||||
#define __NR_preadv 69
|
||||
#define __NR_pwritev 70
|
||||
#define __NR_sendfile 71
|
||||
#define __NR_pselect6 72
|
||||
#define __NR_ppoll 73
|
||||
#define __NR_signalfd4 74
|
||||
#define __NR_vmsplice 75
|
||||
#define __NR_splice 76
|
||||
#define __NR_tee 77
|
||||
#define __NR_readlinkat 78
|
||||
#define __NR_newfstatat 79
|
||||
#define __NR_fstat 80
|
||||
#define __NR_sync 81
|
||||
#define __NR_fsync 82
|
||||
#define __NR_fdatasync 83
|
||||
#define __NR_sync_file_range 84
|
||||
#define __NR_timerfd_create 85
|
||||
#define __NR_timerfd_settime 86
|
||||
#define __NR_timerfd_gettime 87
|
||||
#define __NR_utimensat 88
|
||||
#define __NR_acct 89
|
||||
#define __NR_capget 90
|
||||
#define __NR_capset 91
|
||||
#define __NR_personality 92
|
||||
#define __NR_exit 93
|
||||
#define __NR_exit_group 94
|
||||
#define __NR_waitid 95
|
||||
#define __NR_set_tid_address 96
|
||||
#define __NR_unshare 97
|
||||
#define __NR_futex 98
|
||||
#define __NR_set_robust_list 99
|
||||
#define __NR_get_robust_list 100
|
||||
#define __NR_nanosleep 101
|
||||
#define __NR_getitimer 102
|
||||
#define __NR_setitimer 103
|
||||
#define __NR_kexec_load 104
|
||||
#define __NR_init_module 105
|
||||
#define __NR_delete_module 106
|
||||
#define __NR_timer_create 107
|
||||
#define __NR_timer_gettime 108
|
||||
#define __NR_timer_getoverrun 109
|
||||
#define __NR_timer_settime 110
|
||||
#define __NR_timer_delete 111
|
||||
#define __NR_clock_settime 112
|
||||
#define __NR_clock_gettime 113
|
||||
#define __NR_clock_getres 114
|
||||
#define __NR_clock_nanosleep 115
|
||||
#define __NR_syslog 116
|
||||
#define __NR_ptrace 117
|
||||
#define __NR_sched_setparam 118
|
||||
#define __NR_sched_setscheduler 119
|
||||
#define __NR_sched_getscheduler 120
|
||||
#define __NR_sched_getparam 121
|
||||
#define __NR_sched_setaffinity 122
|
||||
#define __NR_sched_getaffinity 123
|
||||
#define __NR_sched_yield 124
|
||||
#define __NR_sched_get_priority_max 125
|
||||
#define __NR_sched_get_priority_min 126
|
||||
#define __NR_sched_rr_get_interval 127
|
||||
#define __NR_restart_syscall 128
|
||||
#define __NR_kill 129
|
||||
#define __NR_tkill 130
|
||||
#define __NR_tgkill 131
|
||||
#define __NR_sigaltstack 132
|
||||
#define __NR_rt_sigsuspend 133
|
||||
#define __NR_rt_sigaction 134
|
||||
#define __NR_rt_sigprocmask 135
|
||||
#define __NR_rt_sigpending 136
|
||||
#define __NR_rt_sigtimedwait 137
|
||||
#define __NR_rt_sigqueueinfo 138
|
||||
#define __NR_rt_sigreturn 139
|
||||
#define __NR_setpriority 140
|
||||
#define __NR_getpriority 141
|
||||
#define __NR_reboot 142
|
||||
#define __NR_setregid 143
|
||||
#define __NR_setgid 144
|
||||
#define __NR_setreuid 145
|
||||
#define __NR_setuid 146
|
||||
#define __NR_setresuid 147
|
||||
#define __NR_getresuid 148
|
||||
#define __NR_setresgid 149
|
||||
#define __NR_getresgid 150
|
||||
#define __NR_setfsuid 151
|
||||
#define __NR_setfsgid 152
|
||||
#define __NR_times 153
|
||||
#define __NR_setpgid 154
|
||||
#define __NR_getpgid 155
|
||||
#define __NR_getsid 156
|
||||
#define __NR_setsid 157
|
||||
#define __NR_getgroups 158
|
||||
#define __NR_setgroups 159
|
||||
#define __NR_uname 160
|
||||
#define __NR_sethostname 161
|
||||
#define __NR_setdomainname 162
|
||||
#define __NR_getrlimit 163
|
||||
#define __NR_setrlimit 164
|
||||
#define __NR_getrusage 165
|
||||
#define __NR_umask 166
|
||||
#define __NR_prctl 167
|
||||
#define __NR_getcpu 168
|
||||
#define __NR_gettimeofday 169
|
||||
#define __NR_settimeofday 170
|
||||
#define __NR_adjtimex 171
|
||||
#define __NR_getpid 172
|
||||
#define __NR_getppid 173
|
||||
#define __NR_getuid 174
|
||||
#define __NR_geteuid 175
|
||||
#define __NR_getgid 176
|
||||
#define __NR_getegid 177
|
||||
#define __NR_gettid 178
|
||||
#define __NR_sysinfo 179
|
||||
#define __NR_mq_open 180
|
||||
#define __NR_mq_unlink 181
|
||||
#define __NR_mq_timedsend 182
|
||||
#define __NR_mq_timedreceive 183
|
||||
#define __NR_mq_notify 184
|
||||
#define __NR_mq_getsetattr 185
|
||||
#define __NR_msgget 186
|
||||
#define __NR_msgctl 187
|
||||
#define __NR_msgrcv 188
|
||||
#define __NR_msgsnd 189
|
||||
#define __NR_semget 190
|
||||
#define __NR_semctl 191
|
||||
#define __NR_semtimedop 192
|
||||
#define __NR_semop 193
|
||||
#define __NR_shmget 194
|
||||
#define __NR_shmctl 195
|
||||
#define __NR_shmat 196
|
||||
#define __NR_shmdt 197
|
||||
#define __NR_socket 198
|
||||
#define __NR_socketpair 199
|
||||
#define __NR_bind 200
|
||||
#define __NR_listen 201
|
||||
#define __NR_accept 202
|
||||
#define __NR_connect 203
|
||||
#define __NR_getsockname 204
|
||||
#define __NR_getpeername 205
|
||||
#define __NR_sendto 206
|
||||
#define __NR_recvfrom 207
|
||||
#define __NR_setsockopt 208
|
||||
#define __NR_getsockopt 209
|
||||
#define __NR_shutdown 210
|
||||
#define __NR_sendmsg 211
|
||||
#define __NR_recvmsg 212
|
||||
#define __NR_readahead 213
|
||||
#define __NR_brk 214
|
||||
#define __NR_munmap 215
|
||||
#define __NR_mremap 216
|
||||
#define __NR_add_key 217
|
||||
#define __NR_request_key 218
|
||||
#define __NR_keyctl 219
|
||||
#define __NR_clone 220
|
||||
#define __NR_execve 221
|
||||
#define __NR_mmap 222
|
||||
#define __NR_fadvise64 223
|
||||
#define __NR_swapon 224
|
||||
#define __NR_swapoff 225
|
||||
#define __NR_mprotect 226
|
||||
#define __NR_msync 227
|
||||
#define __NR_mlock 228
|
||||
#define __NR_munlock 229
|
||||
#define __NR_mlockall 230
|
||||
#define __NR_munlockall 231
|
||||
#define __NR_mincore 232
|
||||
#define __NR_madvise 233
|
||||
#define __NR_remap_file_pages 234
|
||||
#define __NR_mbind 235
|
||||
#define __NR_get_mempolicy 236
|
||||
#define __NR_set_mempolicy 237
|
||||
#define __NR_migrate_pages 238
|
||||
#define __NR_move_pages 239
|
||||
#define __NR_rt_tgsigqueueinfo 240
|
||||
#define __NR_perf_event_open 241
|
||||
#define __NR_accept4 242
|
||||
#define __NR_recvmmsg 243
|
||||
#define __NR_arch_specific_syscall 244
|
||||
#define __NR_wait4 260
|
||||
#define __NR_prlimit64 261
|
||||
#define __NR_fanotify_init 262
|
||||
#define __NR_fanotify_mark 263
|
||||
#define __NR_name_to_handle_at 264
|
||||
#define __NR_open_by_handle_at 265
|
||||
#define __NR_clock_adjtime 266
|
||||
#define __NR_syncfs 267
|
||||
#define __NR_setns 268
|
||||
#define __NR_sendmmsg 269
|
||||
#define __NR_process_vm_readv 270
|
||||
#define __NR_process_vm_writev 271
|
||||
#define __NR_kcmp 272
|
||||
#define __NR_finit_module 273
|
||||
#define __NR_sched_setattr 274
|
||||
#define __NR_sched_getattr 275
|
||||
#define __NR_renameat2 276
|
||||
#define __NR_seccomp 277
|
||||
#define __NR_getrandom 278
|
||||
#define __NR_memfd_create 279
|
||||
#define __NR_bpf 280
|
||||
#define __NR_execveat 281
|
||||
#define __NR_userfaultfd 282
|
||||
#define __NR_membarrier 283
|
||||
#define __NR_mlock2 284
|
||||
#define __NR_copy_file_range 285
|
||||
#define __NR_preadv2 286
|
||||
#define __NR_pwritev2 287
|
||||
#define __NR_pkey_mprotect 288
|
||||
#define __NR_pkey_alloc 289
|
||||
#define __NR_pkey_free 290
|
||||
#define __NR_statx 291
|
||||
#define __NR_io_pgetevents 292
|
||||
#define __NR_rseq 293
|
||||
#define __NR_kexec_file_load 294
|
||||
#define __NR_pidfd_send_signal 424
|
||||
#define __NR_io_uring_setup 425
|
||||
#define __NR_io_uring_enter 426
|
||||
#define __NR_io_uring_register 427
|
||||
#define __NR_open_tree 428
|
||||
#define __NR_move_mount 429
|
||||
#define __NR_fsopen 430
|
||||
#define __NR_fsconfig 431
|
||||
#define __NR_fsmount 432
|
||||
#define __NR_fspick 433
|
||||
#define __NR_pidfd_open 434
|
||||
#define __NR_clone3 435
|
||||
#define __NR_close_range 436
|
||||
#define __NR_openat2 437
|
||||
#define __NR_pidfd_getfd 438
|
||||
#define __NR_faccessat2 439
|
||||
#define __NR_process_madvise 440
|
||||
#define __NR_epoll_pwait2 441
|
||||
#define __NR_mount_setattr 442
|
||||
#define __NR_landlock_create_ruleset 444
|
||||
#define __NR_landlock_add_rule 445
|
||||
#define __NR_landlock_restrict_self 446
|
||||
|
||||
#define __NR_sysriscv __NR_arch_specific_syscall
|
||||
#define __NR_riscv_flush_icache (__NR_sysriscv + 15)
|
||||
@@ -0,0 +1,4 @@
|
||||
// https://git.musl-libc.org/cgit/musl/plain/arch/riscv64/bits/syscall.h.in
|
||||
#define __riscv64__
|
||||
#include <riscv64/syscalls.h>
|
||||
#include <common.h>
|
||||
@@ -44,6 +44,7 @@
|
||||
#define SYS_clone2 __NR_clone2
|
||||
#define SYS_clone3 __NR_clone3
|
||||
#define SYS_close __NR_close
|
||||
#define SYS_close_range __NR_close_range
|
||||
#define SYS_connect __NR_connect
|
||||
#define SYS_copy_file_range __NR_copy_file_range
|
||||
#define SYS_creat __NR_creat
|
||||
@@ -58,6 +59,7 @@
|
||||
#define SYS_epoll_ctl __NR_epoll_ctl
|
||||
#define SYS_epoll_ctl_old __NR_epoll_ctl_old
|
||||
#define SYS_epoll_pwait __NR_epoll_pwait
|
||||
#define SYS_epoll_pwait2 __NR_epoll_pwait2
|
||||
#define SYS_epoll_wait __NR_epoll_wait
|
||||
#define SYS_epoll_wait_old __NR_epoll_wait_old
|
||||
#define SYS_eventfd __NR_eventfd
|
||||
@@ -69,6 +71,7 @@
|
||||
#define SYS_exit __NR_exit
|
||||
#define SYS_exit_group __NR_exit_group
|
||||
#define SYS_faccessat __NR_faccessat
|
||||
#define SYS_faccessat2 __NR_faccessat2
|
||||
#define SYS_fadvise64 __NR_fadvise64
|
||||
#define SYS_fadvise64_64 __NR_fadvise64_64
|
||||
#define SYS_fallocate __NR_fallocate
|
||||
@@ -191,6 +194,9 @@
|
||||
#define SYS_kexec_load __NR_kexec_load
|
||||
#define SYS_keyctl __NR_keyctl
|
||||
#define SYS_kill __NR_kill
|
||||
#define SYS_landlock_add_rule __NR_landlock_add_rule
|
||||
#define SYS_landlock_create_ruleset __NR_landlock_create_ruleset
|
||||
#define SYS_landlock_restrict_self __NR_landlock_restrict_self
|
||||
#define SYS_lchown __NR_lchown
|
||||
#define SYS_lchown32 __NR_lchown32
|
||||
#define SYS_lgetxattr __NR_lgetxattr
|
||||
@@ -228,6 +234,7 @@
|
||||
#define SYS_mmap2 __NR_mmap2
|
||||
#define SYS_modify_ldt __NR_modify_ldt
|
||||
#define SYS_mount __NR_mount
|
||||
#define SYS_mount_setattr __NR_mount_setattr
|
||||
#define SYS_move_mount __NR_move_mount
|
||||
#define SYS_move_pages __NR_move_pages
|
||||
#define SYS_mprotect __NR_mprotect
|
||||
@@ -402,6 +409,7 @@
|
||||
#define SYS_preadv __NR_preadv
|
||||
#define SYS_preadv2 __NR_preadv2
|
||||
#define SYS_prlimit64 __NR_prlimit64
|
||||
#define SYS_process_madvise __NR_process_madvise
|
||||
#define SYS_process_vm_readv __NR_process_vm_readv
|
||||
#define SYS_process_vm_writev __NR_process_vm_writev
|
||||
#define SYS_prof __NR_prof
|
||||
@@ -440,6 +448,7 @@
|
||||
#define SYS_reserved221 __NR_reserved221
|
||||
#define SYS_reserved82 __NR_reserved82
|
||||
#define SYS_restart_syscall __NR_restart_syscall
|
||||
#define SYS_riscv_flush_icache __NR_riscv_flush_icache
|
||||
#define SYS_rmdir __NR_rmdir
|
||||
#define SYS_rseq __NR_rseq
|
||||
#define SYS_rtas __NR_rtas
|
||||
@@ -572,6 +581,7 @@
|
||||
#define SYS_sys_kexec_load __NR_sys_kexec_load
|
||||
#define SYS_syslog __NR_syslog
|
||||
#define SYS_sysmips __NR_sysmips
|
||||
#define SYS_sysriscv __NR_sysriscv
|
||||
#define SYS_sys_setaltroot __NR_sys_setaltroot
|
||||
#define SYS_tee __NR_tee
|
||||
#define SYS_tgkill __NR_tgkill
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -7,4 +7,7 @@ all: generate.py functions.py
|
||||
functions.py:
|
||||
wget https://raw.githubusercontent.com/zachriggle/functions/master/functions.py
|
||||
|
||||
generate_darwin:
|
||||
python3 generate_darwin.py "$(ROOT)"
|
||||
|
||||
.phony: all
|
||||
@@ -72,7 +72,7 @@ CALL = """
|
||||
target = regs[index]
|
||||
register_arguments[target] = arg
|
||||
elif arg is not None:
|
||||
stack_arguments[index] = arg
|
||||
stack_arguments[name] = arg
|
||||
|
||||
# The argument is not a register. It is a string value, and we
|
||||
# are expecting a string value
|
||||
@@ -100,7 +100,7 @@ CALL = """
|
||||
target = regs[index]
|
||||
register_arguments[target] = arg
|
||||
elif arg is not None:
|
||||
stack_arguments[target] = arg
|
||||
stack_arguments[name] = arg
|
||||
|
||||
# Some syscalls have different names on various architectures.
|
||||
# Determine which syscall number to use for the current architecture.
|
||||
@@ -108,7 +108,7 @@ CALL = """
|
||||
if hasattr(pwnlib.constants, syscall):
|
||||
break
|
||||
else:
|
||||
raise Exception("Could not locate any syscalls: %r" % syscalls)
|
||||
raise Exception("Could not locate any syscalls: %r" % {syscalls!r})
|
||||
%>
|
||||
/* {name}(${{', '.join(syscall_repr)}}) */
|
||||
%for name, arg in string_arguments.items():
|
||||
@@ -249,7 +249,7 @@ def generate_one(target):
|
||||
# Mako is unable to use *vararg and *kwarg, so we just stub in
|
||||
# a whole bunch of additional arguments.
|
||||
if argname == 'vararg':
|
||||
for j in range(5):
|
||||
for j in range(6):
|
||||
argname = 'vararg_%i' % j
|
||||
argument_names.append(argname)
|
||||
argument_names_.append(argname)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# ./pwnlib/data/includes/darwin/aarch64.h
|
||||
# ./pwnlib/constants/darwin/aarch64.py
|
||||
|
||||
# https://github.com/nullgemm/instant_macos_sdk (old sdk here, please use real macos device)
|
||||
# https://github.com/joseluisq/macosx-sdks (old sdk here, please use real macos device)
|
||||
# /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
||||
# In the future, you should change the version of `MacOSX15.sdk`
|
||||
sdk_path = Path('/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/')
|
||||
if not sdk_path.exists():
|
||||
print('missing MacOSX sdk')
|
||||
exit(1)
|
||||
|
||||
project_path = Path(sys.argv[1])
|
||||
|
||||
regex = re.compile(r'^#define\s+([a-zA-Z0-9_-]+)\s+([0-9]+|0x[0-9a-fA-F]+)(?:\s|$)', re.DOTALL)
|
||||
|
||||
out_data = {}
|
||||
for file in sdk_path.iterdir():
|
||||
if not file.is_file():
|
||||
continue
|
||||
|
||||
print(file.name)
|
||||
for line in file.read_text(errors='ignore').split('\n'):
|
||||
matched = regex.search(line)
|
||||
if not matched:
|
||||
continue
|
||||
|
||||
key, value = matched.groups()
|
||||
if value.startswith('0') and not value.startswith('0x') and len(value) > 1:
|
||||
value = '0o'+value
|
||||
|
||||
print(key, value)
|
||||
out_data[key] = value
|
||||
|
||||
|
||||
outbuf1_aarch64 = ''
|
||||
outbuf1_aarch64 += "from pwnlib.constants.constant import Constant\n"
|
||||
|
||||
outbuf1_amd64 = ''
|
||||
outbuf1_amd64 += "from pwnlib.constants.constant import Constant\n"
|
||||
|
||||
outbuf2_aarch64 = ''
|
||||
outbuf2_amd64 = ''
|
||||
|
||||
# https://www.idryman.org/blog/2014/12/02/writing-64-bit-assembly-on-mac-os-x/
|
||||
# on amd64 syscall offsets from 0x2000000 + syscall number
|
||||
|
||||
for key, value in out_data.items():
|
||||
value_octal = value
|
||||
if value_octal.startswith('0o'):
|
||||
value_octal = value_octal.replace('0o', '0')
|
||||
|
||||
outbuf1_aarch64 += "{} = Constant('{}',{})\n".format(key, key, value)
|
||||
outbuf2_aarch64 += "#define {} {}\n".format(key, value_octal)
|
||||
|
||||
if key.startswith('SYS_'):
|
||||
value = f'{value} + 0x2000000'
|
||||
value_octal = f'{value_octal} + 0x2000000'
|
||||
|
||||
outbuf1_amd64 += "{} = Constant('{}',{})\n".format(key, key, value)
|
||||
outbuf2_amd64 += "#define {} {}\n".format(key, value_octal)
|
||||
|
||||
pp = project_path
|
||||
(pp / Path('./pwnlib/constants/darwin/aarch64.py')).write_bytes(outbuf1_aarch64.encode())
|
||||
(pp / Path('./pwnlib/data/includes/darwin/aarch64.h')).write_bytes(outbuf2_aarch64.encode())
|
||||
|
||||
(pp / Path('./pwnlib/constants/darwin/amd64.py')).write_bytes(outbuf1_amd64.encode())
|
||||
(pp / Path('./pwnlib/data/includes/darwin/amd64.h')).write_bytes(outbuf2_amd64.encode())
|
||||
@@ -1,4 +1,4 @@
|
||||
<%page args="binary, host=None, port=None, user=None, password=None, remote_path=None, quiet=False"/>\
|
||||
<%page args="binary, host=None, port=None, user=None, password=None, libc=None, remote_path=None, quiet=False"/>\
|
||||
<%
|
||||
import os
|
||||
import sys
|
||||
@@ -31,6 +31,7 @@ elif host and not port:
|
||||
remote_path = remote_path or exe
|
||||
password = password or 'secret1234'
|
||||
binary_repr = repr(binary)
|
||||
libc_repr = repr(libc)
|
||||
%>\
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -43,7 +44,7 @@ from pwn import *
|
||||
%if not quiet:
|
||||
# Set up pwntools for the correct architecture
|
||||
%endif
|
||||
%if ctx.binary:
|
||||
%if ctx.binary or not host:
|
||||
exe = context.binary = ELF(args.EXE or ${binary_repr})
|
||||
<% binary_repr = 'exe.path' %>
|
||||
%else:
|
||||
@@ -83,6 +84,31 @@ if not args.LOCAL:
|
||||
shell.set_working_directory(symlink=True)
|
||||
%endif
|
||||
|
||||
%if libc:
|
||||
%if not quiet:
|
||||
# Use the specified remote libc version unless explicitly told to use the
|
||||
# local system version with the `LOCAL_LIBC` argument.
|
||||
# ./exploit.py LOCAL LOCAL_LIBC
|
||||
%endif
|
||||
if args.LOCAL_LIBC:
|
||||
libc = exe.libc
|
||||
%if host:
|
||||
elif args.LOCAL:
|
||||
%else:
|
||||
else:
|
||||
%endif
|
||||
library_path = libcdb.download_libraries(${libc_repr})
|
||||
if library_path:
|
||||
exe = context.binary = ELF.patch_custom_libraries(${binary_repr}, library_path)
|
||||
libc = exe.libc
|
||||
else:
|
||||
libc = ELF(${libc_repr})
|
||||
%if host:
|
||||
else:
|
||||
libc = ELF(${libc_repr})
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%if host:
|
||||
def start_local(argv=[], *a, **kw):
|
||||
'''Execute the target binary locally'''
|
||||
|
||||
+137
-49
@@ -165,6 +165,18 @@ class DynELF(object):
|
||||
self._waitfor = None
|
||||
self._bases = {}
|
||||
self._dynamic = None
|
||||
self.elf = None
|
||||
|
||||
if elf:
|
||||
path = elf
|
||||
if isinstance(elf, ELF):
|
||||
path = elf.path
|
||||
|
||||
# Load a fresh copy of the ELF
|
||||
with context.local(log_level='error'):
|
||||
w = self.waitfor("Loading from %r" % path)
|
||||
self.elf = ELF(path)
|
||||
w.success("[LOADED]")
|
||||
|
||||
if not (pointer or (elf and elf.address)):
|
||||
log.error("Must specify either a pointer into a module and/or an ELF file with a valid base address")
|
||||
@@ -177,12 +189,15 @@ class DynELF(object):
|
||||
if not elf:
|
||||
log.warn_once("No ELF provided. Leaking is much faster if you have a copy of the ELF being leaked.")
|
||||
|
||||
self.elf = elf
|
||||
self.leak = leak
|
||||
self.libbase = self._find_base(pointer or elf.address)
|
||||
|
||||
if elf:
|
||||
self._find_linkmap_assisted(elf)
|
||||
self._elftype = self.elf.elftype
|
||||
self._elfclass = self.elf.elfclass
|
||||
self.elf.address = self.libbase
|
||||
self._dynamic = self.elf.get_section_by_name('.dynamic').header.sh_addr
|
||||
self._dynamic = self._make_absolute_ptr(self._dynamic)
|
||||
|
||||
@classmethod
|
||||
def for_one_lib_only(cls, leak, ptr):
|
||||
@@ -241,49 +256,6 @@ class DynELF(object):
|
||||
self._dynamic = self._find_dynamic_phdr()
|
||||
return self._dynamic
|
||||
|
||||
def _find_linkmap_assisted(self, path):
|
||||
"""Uses an ELF file to assist in finding the link_map.
|
||||
"""
|
||||
if isinstance(path, ELF):
|
||||
path = path.path
|
||||
|
||||
# Load a fresh copy of the ELF
|
||||
with context.local(log_level='error'):
|
||||
elf = ELF(path)
|
||||
elf.address = self.libbase
|
||||
|
||||
w = self.waitfor("Loading from %r" % elf.path)
|
||||
|
||||
# Save our real leaker
|
||||
real_leak = self.leak
|
||||
|
||||
# Create a fake leaker which just leaks out of the 'loaded' ELF
|
||||
# However, we may load things which are outside of the ELF (e.g.
|
||||
# the linkmap or GOT) so we need to fall back on the real leak.
|
||||
@MemLeak
|
||||
def fake_leak(address):
|
||||
try:
|
||||
return elf.read(address, 4)
|
||||
except ValueError:
|
||||
return real_leak.b(address)
|
||||
|
||||
# Save off our real leaker, use the fake leaker
|
||||
self.leak = fake_leak
|
||||
|
||||
# Get useful pointers for resolving the linkmap faster
|
||||
w.status("Searching for DT_PLTGOT")
|
||||
pltgot = self._find_dt(constants.DT_PLTGOT)
|
||||
|
||||
w.status("Searching for DT_DEBUG")
|
||||
debug = self._find_dt(constants.DT_DEBUG)
|
||||
|
||||
# Restore the real leaker
|
||||
self.leak = real_leak
|
||||
|
||||
# Find the linkmap using the helper pointers
|
||||
self._find_linkmap(pltgot, debug)
|
||||
self.success('Done')
|
||||
|
||||
def _find_base(self, ptr):
|
||||
page_size = 0x1000
|
||||
page_mask = ~(page_size - 1)
|
||||
@@ -380,6 +352,27 @@ class DynELF(object):
|
||||
|
||||
return dynamic
|
||||
|
||||
def _find_dt_optimized(self, name):
|
||||
"""
|
||||
Find an entry in the DYNAMIC array through an ELF
|
||||
|
||||
Arguments:
|
||||
name(str): Name of the tag to find ('DT_DEBUG', 'DT_PLTGOT', ...)
|
||||
|
||||
Returns:
|
||||
Pointer to the data described by the specified entry.
|
||||
"""
|
||||
if not self.elf:
|
||||
return None
|
||||
|
||||
ptr = self.elf.dynamic_value_by_tag(name)
|
||||
if ptr:
|
||||
ptr = self._make_absolute_ptr(ptr)
|
||||
self.success("Found %s at %#x" % (name, ptr))
|
||||
return ptr
|
||||
return None
|
||||
|
||||
|
||||
def _find_dt(self, tag):
|
||||
"""
|
||||
Find an entry in the DYNAMIC array.
|
||||
@@ -390,11 +383,16 @@ class DynELF(object):
|
||||
Returns:
|
||||
Pointer to the data described by the specified entry.
|
||||
"""
|
||||
leak = self.leak
|
||||
base = self.libbase
|
||||
dynamic = self.dynamic
|
||||
leak = self.leak
|
||||
name = next(k for k,v in ENUM_D_TAG.items() if v == tag)
|
||||
|
||||
# Read directly from the ELF if possible
|
||||
ptr = self._find_dt_optimized(name)
|
||||
if ptr:
|
||||
return ptr
|
||||
|
||||
Dyn = {32: elf.Elf32_Dyn, 64: elf.Elf64_Dyn} [self.elfclass]
|
||||
|
||||
# Found the _DYNAMIC program header, now find PLTGOT entry in it
|
||||
@@ -407,10 +405,10 @@ class DynELF(object):
|
||||
self.failure("Could not find tag %s" % name)
|
||||
return None
|
||||
|
||||
self.status("Found %s at %#x" % (name, dynamic))
|
||||
ptr = leak.field(dynamic, Dyn.d_ptr)
|
||||
|
||||
ptr = self._make_absolute_ptr(ptr)
|
||||
self.status("Found %s at %#x" % (name, ptr))
|
||||
|
||||
return ptr
|
||||
|
||||
@@ -599,6 +597,10 @@ class DynELF(object):
|
||||
Return a dictionary mapping library path to its base address.
|
||||
'''
|
||||
if not self._bases:
|
||||
if self.link_map is None:
|
||||
self.failure("Cannot determine bases without linkmap")
|
||||
return {}
|
||||
|
||||
leak = self.leak
|
||||
LinkMap = {32: elf.Elf32_Link_Map, 64: elf.Elf64_Link_Map}[self.elfclass]
|
||||
|
||||
@@ -650,7 +652,7 @@ class DynELF(object):
|
||||
break
|
||||
|
||||
if name:
|
||||
self.status('Skipping %s' % name)
|
||||
self.status('Skipping %r' % name)
|
||||
|
||||
cur = leak.field(cur, LinkMap.l_next)
|
||||
else:
|
||||
@@ -666,6 +668,62 @@ class DynELF(object):
|
||||
lib._waitfor = self._waitfor
|
||||
return lib
|
||||
|
||||
def _rel_lookup(self, symb, strtab=None, symtab=None, jmprel=None):
|
||||
"""Performs slower symbol lookup using DT_JMPREL(.rela.plt)"""
|
||||
leak = self.leak
|
||||
elf_obj = self.elf
|
||||
symb_name = symb.decode()
|
||||
|
||||
# If elf is available look for the symbol in it
|
||||
if elf_obj and symb_name in elf_obj.symbols:
|
||||
self.success("Symbol '%s' found in ELF!" % symb_name)
|
||||
return elf_obj.symbols[symb_name]
|
||||
|
||||
log.warning("Looking up symbol through DT_JMPREL. This might be slower...")
|
||||
|
||||
|
||||
strtab = strtab or self._find_dt(constants.DT_STRTAB)
|
||||
symtab = symtab or self._find_dt(constants.DT_SYMTAB)
|
||||
jmprel = jmprel or self._find_dt(constants.DT_JMPREL) # .rela.plt
|
||||
|
||||
strtab = self._make_absolute_ptr(strtab)
|
||||
symtab = self._make_absolute_ptr(symtab)
|
||||
jmprel = self._make_absolute_ptr(jmprel)
|
||||
|
||||
w = self.waitfor("Looking for %s in .rel.plt" % symb)
|
||||
# We look for the symbol by iterating through each Elf64_Rel entry.
|
||||
# For each Elf64_Rel, get the Elf64_Sym for that entry
|
||||
# Then compare the Elf64_Sym.st_name with the symbol name
|
||||
|
||||
Rel = {32: elf.Elf32_Rel, 64: elf.Elf64_Rel}[self.elfclass]
|
||||
Sym = {32: elf.Elf32_Sym, 64: elf.Elf64_Sym}[self.elfclass]
|
||||
|
||||
rel_addr = jmprel
|
||||
rel_entry = None
|
||||
while True:
|
||||
rel_entry = leak.struct(rel_addr, Rel)
|
||||
|
||||
# We ran out of entries in DT_JMPREL
|
||||
if rel_entry.r_offset == 0:
|
||||
return None
|
||||
|
||||
sym_idx = rel_entry.r_info >> 32 # might be different for 32-bit
|
||||
sym_entry_address = symtab + ( sym_idx * sizeof(Sym) )
|
||||
sym_str_off = leak.field(sym_entry_address, Sym.st_name)
|
||||
symb_str = leak.s(strtab+sym_str_off)
|
||||
|
||||
if symb_str == symb:
|
||||
w.success("Found matching Elf64_Rel entry!")
|
||||
break
|
||||
|
||||
rel_addr += sizeof(Rel)
|
||||
|
||||
symbol_address = self._make_absolute_ptr(rel_entry.r_offset)
|
||||
|
||||
return symbol_address
|
||||
|
||||
|
||||
|
||||
def _lookup(self, symb):
|
||||
"""Performs the actual symbol lookup within one ELF file."""
|
||||
leak = self.leak
|
||||
@@ -698,9 +756,39 @@ class DynELF(object):
|
||||
#
|
||||
# Perform the hash lookup
|
||||
#
|
||||
|
||||
# Save off our real leaker in case we use the fake leaker
|
||||
real_leak = self.leak
|
||||
if self.elf:
|
||||
|
||||
# Create a fake leaker which just leaks out of the 'loaded' ELF
|
||||
# However, we may load things which are outside of the ELF (e.g.
|
||||
# the linkmap or GOT) so we need to fall back on the real leak.
|
||||
@MemLeak
|
||||
def fake_leak(address):
|
||||
try:
|
||||
return self.elf.read(address, 4)
|
||||
except ValueError:
|
||||
return real_leak.b(address)
|
||||
# Use fake leaker since ELF is available
|
||||
self.leak = fake_leak
|
||||
|
||||
routine = {'sysv': self._resolve_symbol_sysv,
|
||||
'gnu': self._resolve_symbol_gnu}[hshtype]
|
||||
return routine(self.libbase, symb, hshtab, strtab, symtab)
|
||||
resolved_addr = routine(self.libbase, symb, hshtab, strtab, symtab)
|
||||
|
||||
if resolved_addr:
|
||||
# Restore the original leaker
|
||||
self.leak = real_leak
|
||||
return resolved_addr
|
||||
|
||||
# if symbol not found in GNU_Hash, try looking in JMPREL
|
||||
resolved_addr = self._rel_lookup(symb, strtab, symtab)
|
||||
|
||||
# Restore the original leaker
|
||||
self.leak = real_leak
|
||||
|
||||
return resolved_addr
|
||||
|
||||
def _resolve_symbol_sysv(self, libbase, symb, hshtab, strtab, symtab):
|
||||
"""
|
||||
|
||||
+37
-77
@@ -93,7 +93,6 @@ from pwnlib.util.fiddling import enhex
|
||||
from pwnlib.util.fiddling import unhex
|
||||
from pwnlib.util.misc import read
|
||||
from pwnlib.util.misc import write
|
||||
from pwnlib.util.packing import _decode
|
||||
from pwnlib.util.packing import pack
|
||||
from pwnlib.util.packing import unpack_many
|
||||
|
||||
@@ -106,44 +105,11 @@ prstatus_types = {
|
||||
'aarch64': elf_prstatus_aarch64
|
||||
}
|
||||
|
||||
prpsinfo_types = {
|
||||
32: elf_prpsinfo_32,
|
||||
64: elf_prpsinfo_64,
|
||||
}
|
||||
|
||||
siginfo_types = {
|
||||
32: elf_siginfo_32,
|
||||
64: elf_siginfo_64
|
||||
}
|
||||
|
||||
# Slightly modified copy of the pyelftools version of the same function,
|
||||
# until they fix this issue:
|
||||
# https://github.com/eliben/pyelftools/issues/93
|
||||
def iter_notes(self):
|
||||
""" Iterates the list of notes in the segment.
|
||||
"""
|
||||
offset = self['p_offset']
|
||||
end = self['p_offset'] + self['p_filesz']
|
||||
while offset < end:
|
||||
note = struct_parse(
|
||||
self.elffile.structs.Elf_Nhdr,
|
||||
self.stream,
|
||||
stream_pos=offset)
|
||||
note['n_offset'] = offset
|
||||
offset += self.elffile.structs.Elf_Nhdr.sizeof()
|
||||
self.stream.seek(offset)
|
||||
# n_namesz is 4-byte aligned.
|
||||
disk_namesz = roundup(note['n_namesz'], 2)
|
||||
with context.local(encoding='latin-1'):
|
||||
note['n_name'] = _decode(
|
||||
CString('').parse(self.stream.read(disk_namesz)))
|
||||
offset += disk_namesz
|
||||
|
||||
desc_data = _decode(self.stream.read(note['n_descsz']))
|
||||
note['n_desc'] = desc_data
|
||||
offset += roundup(note['n_descsz'], 2)
|
||||
note['n_size'] = offset - note['n_offset']
|
||||
yield note
|
||||
|
||||
class Mapping(object):
|
||||
"""Encapsulates information about a memory mapping in a :class:`Corefile`.
|
||||
@@ -272,8 +238,8 @@ class Corefile(ELF):
|
||||
Registers can be accessed directly, e.g. via ``core_obj.eax`` and enumerated
|
||||
via :data:`Corefile.registers`.
|
||||
|
||||
Memory can be accessed directly via :meth:`.read` or :meth:`.write`, and also
|
||||
via :meth:`.pack` or :meth:`.unpack` or even :meth:`.string`.
|
||||
Memory can be accessed directly via :meth:`pwnlib.elf.elf.ELF.read` or :meth:`pwnlib.elf.elf.ELF.write`, and also
|
||||
via :meth:`pwnlib.elf.elf.ELF.pack` or :meth:`pwnlib.elf.elf.ELF.unpack` or even :meth:`.string`.
|
||||
|
||||
Arguments:
|
||||
core: Path to the core file. Alternately, may be a :class:`.process` instance,
|
||||
@@ -410,8 +376,8 @@ class Corefile(ELF):
|
||||
>>> core.exe.data[0:4]
|
||||
b'\x7fELF'
|
||||
|
||||
It also supports all of the features of :class:`ELF`, so you can :meth:`.read`
|
||||
or :meth:`.write` or even the helpers like :meth:`.pack` or :meth:`.unpack`.
|
||||
It also supports all of the features of :class:`ELF`, so you can :meth:`pwnlib.elf.elf.ELF.read`
|
||||
or :meth:`pwnlib.elf.elf.ELF.write` or even the helpers like :meth:`pwnlib.elf.elf.ELF.pack` or :meth:`pwnlib.elf.elf.ELF.unpack`.
|
||||
|
||||
Don't forget to call :meth:`.ELF.save` to save the changes to disk.
|
||||
|
||||
@@ -531,7 +497,6 @@ class Corefile(ELF):
|
||||
>>> io = elf.process()
|
||||
>>> io.wait(2)
|
||||
>>> core = io.corefile
|
||||
[!] End of the stack is corrupted, skipping stack parsing (got: 41414141)
|
||||
>>> core.argc, core.argv, core.env
|
||||
(0, [], {})
|
||||
>>> core.stack.data.endswith(b'AAAA')
|
||||
@@ -590,6 +555,9 @@ class Corefile(ELF):
|
||||
# Pointer to the entry point
|
||||
self.at_entry = 0
|
||||
|
||||
# Pointer to the vdso
|
||||
self.at_sysinfo_ehdr = None
|
||||
|
||||
try:
|
||||
super(Corefile, self).__init__(*a, **kw)
|
||||
except IOError:
|
||||
@@ -606,7 +574,6 @@ class Corefile(ELF):
|
||||
log.warn_once("%s does not use a supported corefile architecture, registers are unavailable" % self.file.name)
|
||||
|
||||
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:
|
||||
@@ -617,45 +584,38 @@ class Corefile(ELF):
|
||||
continue
|
||||
|
||||
|
||||
# Note that older versions of pyelftools (<=0.24) are missing enum values
|
||||
# for NT_PRSTATUS, NT_PRPSINFO, NT_AUXV, etc.
|
||||
# For this reason, we have to check if note.n_type is any of several values.
|
||||
for note in iter_notes(segment):
|
||||
if not isinstance(note.n_desc, bytes):
|
||||
note['n_desc'] = note.n_desc.encode('latin1')
|
||||
for note in segment.iter_notes():
|
||||
# Try to find NT_PRSTATUS.
|
||||
if prstatus_type and \
|
||||
note.n_descsz == ctypes.sizeof(prstatus_type) and \
|
||||
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRSTATUS'):
|
||||
if note.n_type == 'NT_PRSTATUS':
|
||||
self.NT_PRSTATUS = note
|
||||
self.prstatus = prstatus_type.from_buffer_copy(note.n_desc)
|
||||
|
||||
# Try to find NT_PRPSINFO
|
||||
if prpsinfo_type and \
|
||||
note.n_descsz == ctypes.sizeof(prpsinfo_type) and \
|
||||
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRPSINFO'):
|
||||
if note.n_type == 'NT_PRPSINFO':
|
||||
self.NT_PRPSINFO = note
|
||||
self.prpsinfo = prpsinfo_type.from_buffer_copy(note.n_desc)
|
||||
self.prpsinfo = note.n_desc
|
||||
|
||||
# Try to find NT_SIGINFO so we can see the fault
|
||||
if note.n_type in (0x53494749, 'NT_SIGINFO'):
|
||||
if note.n_type == 'NT_SIGINFO':
|
||||
self.NT_SIGINFO = note
|
||||
self.siginfo = siginfo_type.from_buffer_copy(note.n_desc)
|
||||
|
||||
# Try to find the list of mapped files
|
||||
if note.n_type in (constants.NT_FILE, 'NT_FILE'):
|
||||
if note.n_type == 'NT_FILE':
|
||||
with context.local(bytes=self.bytes):
|
||||
self._parse_nt_file(note)
|
||||
|
||||
# Try to find the auxiliary vector, which will tell us
|
||||
# where the top of the stack is.
|
||||
if note.n_type in (constants.NT_AUXV, 'NT_AUXV'):
|
||||
if note.n_type == 'NT_AUXV':
|
||||
self.NT_AUXV = note
|
||||
with context.local(bytes=self.bytes):
|
||||
self._parse_auxv(note)
|
||||
|
||||
if not self.stack and self.mappings:
|
||||
self.stack = self.mappings[-1].stop
|
||||
if self.mappings[-1].start == 0xffffffffff600000 and len(self.mappings) > 1:
|
||||
self.stack = self.mappings[-2].stop
|
||||
|
||||
if self.stack and self.mappings:
|
||||
for mapping in self.mappings:
|
||||
@@ -667,7 +627,7 @@ class Corefile(ELF):
|
||||
log.warn('Could not find the stack!')
|
||||
self.stack = None
|
||||
|
||||
with context.local(bytes=self.bytes, log_level='warn'):
|
||||
with context.local(bytes=self.bytes):
|
||||
try:
|
||||
self._parse_stack()
|
||||
except ValueError:
|
||||
@@ -685,31 +645,16 @@ class Corefile(ELF):
|
||||
self._describe_core()
|
||||
|
||||
def _parse_nt_file(self, note):
|
||||
t = tube()
|
||||
t.unrecv(note.n_desc)
|
||||
|
||||
count = t.unpack()
|
||||
page_size = t.unpack()
|
||||
|
||||
starts = []
|
||||
addresses = {}
|
||||
|
||||
for i in range(count):
|
||||
start = t.unpack()
|
||||
end = t.unpack()
|
||||
offset = t.unpack()
|
||||
starts.append((start, offset))
|
||||
|
||||
for i in range(count):
|
||||
filename = t.recvuntil(b'\x00', drop=True)
|
||||
for vma, filename in zip(note.n_desc.Elf_Nt_File_Entry, note.n_desc.filename):
|
||||
if not isinstance(filename, str):
|
||||
filename = filename.decode('utf-8')
|
||||
(start, offset) = starts[i]
|
||||
|
||||
filename = filename.decode('utf-8', 'surrogateescape')
|
||||
for mapping in self.mappings:
|
||||
if mapping.start == start:
|
||||
if mapping.start == vma.vm_start:
|
||||
mapping.name = filename
|
||||
mapping.page_offset = offset
|
||||
mapping.page_offset = vma.page_offset
|
||||
|
||||
self.mappings = sorted(self.mappings, key=lambda m: m.start)
|
||||
|
||||
@@ -1389,6 +1334,10 @@ class CorefileFinder(object):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Convert bytes-like object to string
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode('utf-8')
|
||||
|
||||
return data
|
||||
|
||||
def systemd_coredump_corefile(self):
|
||||
@@ -1566,7 +1515,18 @@ class CorefileFinder(object):
|
||||
# should be unique enough that we can just glob.
|
||||
|
||||
boot_id = read('/proc/sys/kernel/random/boot_id').strip().decode()
|
||||
path = self.exe.replace('/', '_')
|
||||
|
||||
# Use the absolute path of the executable
|
||||
# Apport uses the executable's path to determine the core dump filename
|
||||
#
|
||||
# Reference source:
|
||||
# https://github.com/canonical/apport/blob/4bbb179b8f92989bf7c1ee3692074f35d70ef3e8/data/apport#L110
|
||||
# https://github.com/canonical/apport/blob/4bbb179b8f92989bf7c1ee3692074f35d70ef3e8/apport/fileutils.py#L599
|
||||
#
|
||||
# Apport calls `get_core_path` with `options.executable_path`, which corresponds to
|
||||
# the executable's pathname, as specified by the `%E` placeholder
|
||||
# in the core pattern (see `man core` and `apport --help`).
|
||||
path = os.path.abspath(self.exe).replace('/', '_').replace('.', '_')
|
||||
|
||||
# Format the name
|
||||
corefile_name = 'core.{path}.{uid}.{boot_id}.{pid}.*'.format(
|
||||
|
||||
+9
-23
@@ -399,6 +399,15 @@ class Elf64_Sym(ctypes.Structure):
|
||||
("st_value", Elf64_Addr),
|
||||
("st_size", Elf64_Xword),]
|
||||
|
||||
class Elf64_Rel(ctypes.Structure):
|
||||
_fields_ = [("r_offset", Elf64_Addr),
|
||||
("r_info", Elf64_Xword),
|
||||
("r_addend", Elf64_Sxword),]
|
||||
|
||||
class Elf32_Rel(ctypes.Structure):
|
||||
_fields_ = [("r_offset", Elf32_Addr),
|
||||
("r_info", Elf32_Word),]
|
||||
|
||||
class Elf32_Link_Map(ctypes.Structure):
|
||||
_fields_ = [("l_addr", Elf32_Addr),
|
||||
("l_name", Elf32_Addr),
|
||||
@@ -631,29 +640,6 @@ class Elf64_auxv_t(ctypes.Structure):
|
||||
_fields_ = [('a_type', ctypes.c_uint64),
|
||||
('a_val', ctypes.c_uint64),]
|
||||
|
||||
def generate_prpsinfo(long):
|
||||
return [
|
||||
('pr_state', byte),
|
||||
('pr_sname', char),
|
||||
('pr_zomb', byte),
|
||||
('pr_nice', byte),
|
||||
('pr_flag', long),
|
||||
('pr_uid', ctypes.c_ushort),
|
||||
('pr_gid', ctypes.c_ushort),
|
||||
('pr_pid', ctypes.c_int),
|
||||
('pr_ppid', ctypes.c_int),
|
||||
('pr_pgrp', ctypes.c_int),
|
||||
('pr_sid', ctypes.c_int),
|
||||
('pr_fname', char * 16),
|
||||
('pr_psargs', char * 80)
|
||||
]
|
||||
|
||||
class elf_prpsinfo_32(ctypes.Structure):
|
||||
_fields_ = generate_prpsinfo(Elf32_Addr)
|
||||
|
||||
class elf_prpsinfo_64(ctypes.Structure):
|
||||
_fields_ = generate_prpsinfo(Elf64_Addr)
|
||||
|
||||
def generate_siginfo(int_t, long_t):
|
||||
class siginfo_t(ctypes.Structure):
|
||||
_fields_ = [('si_signo', int_t),
|
||||
|
||||
+367
-107
@@ -47,14 +47,16 @@ import tempfile
|
||||
|
||||
from six import BytesIO
|
||||
|
||||
from collections import namedtuple
|
||||
from collections import namedtuple, defaultdict
|
||||
|
||||
from elftools.elf.constants import P_FLAGS
|
||||
from elftools.elf.constants import SHN_INDICES
|
||||
from elftools.elf.descriptions import describe_e_type
|
||||
from elftools.elf.dynamic import DynamicSection
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.enums import ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS
|
||||
from elftools.elf.gnuversions import GNUVerDefSection
|
||||
from elftools.elf.relocation import RelocationSection
|
||||
from elftools.elf.relocation import RelocationSection, RelrRelocationSection
|
||||
from elftools.elf.sections import SymbolTableSection
|
||||
from elftools.elf.segments import InterpSegment
|
||||
|
||||
@@ -82,7 +84,7 @@ 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.misc import align, align_down, which
|
||||
from pwnlib.util.sh_string import sh_string
|
||||
|
||||
log = getLogger(__name__)
|
||||
@@ -357,12 +359,21 @@ class ELF(ELFFile):
|
||||
self._populate_functions()
|
||||
self._populate_kernel_version()
|
||||
|
||||
self._print_checksec = checksec
|
||||
if checksec:
|
||||
self._describe()
|
||||
|
||||
self._libs = None
|
||||
self._maps = None
|
||||
|
||||
def close(self):
|
||||
"""close() -> None
|
||||
|
||||
Close the ELF file and release all resources associated with it.
|
||||
"""
|
||||
super(ELF, self).close()
|
||||
self.file.close()
|
||||
|
||||
@staticmethod
|
||||
@LocalContext
|
||||
def from_assembly(assembly, *a, **kw):
|
||||
@@ -447,7 +458,7 @@ class ELF(ELFFile):
|
||||
|
||||
def _describe(self, *a, **kw):
|
||||
log.info_once(
|
||||
'%s\n%-10s%s-%s-%s\n%s',
|
||||
'%s\n%-12s%s-%s-%s\n%s',
|
||||
repr(self.path),
|
||||
'Arch:',
|
||||
self.arch,
|
||||
@@ -459,6 +470,7 @@ class ELF(ELFFile):
|
||||
def get_machine_arch(self):
|
||||
return {
|
||||
('EM_X86_64', 64): 'amd64',
|
||||
('EM_X86_64', 32): 'amd64', # x32 ABI
|
||||
('EM_386', 32): 'i386',
|
||||
('EM_486', 32): 'i386',
|
||||
('EM_ARM', 32): 'arm',
|
||||
@@ -510,6 +522,29 @@ class ELF(ELFFile):
|
||||
if t == seg.header.p_type or t in str(seg.header.p_type):
|
||||
yield seg
|
||||
|
||||
def iter_notes(self):
|
||||
"""
|
||||
Yields:
|
||||
All the notes in the PT_NOTE segments. Each result is a dictionary-
|
||||
like object with ``n_name``, ``n_type``, and ``n_desc`` fields, amongst
|
||||
others.
|
||||
"""
|
||||
for seg in self.iter_segments_by_type('PT_NOTE'):
|
||||
for note in seg.iter_notes():
|
||||
yield note
|
||||
|
||||
def iter_properties(self):
|
||||
"""
|
||||
Yields:
|
||||
All the GNU properties in the PT_NOTE segments. Each result is a dictionary-
|
||||
like object with ``pr_type``, ``pr_datasz``, and ``pr_data`` fields.
|
||||
"""
|
||||
for note in self.iter_notes():
|
||||
if note.n_type != 'NT_GNU_PROPERTY_TYPE_0':
|
||||
continue
|
||||
for prop in note.n_desc:
|
||||
yield prop
|
||||
|
||||
def get_segment_for_address(self, address, size=1):
|
||||
"""get_segment_for_address(address, size=1) -> Segment
|
||||
|
||||
@@ -680,29 +715,57 @@ class ELF(ELFFile):
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
"""Dictionary of {path: address} for every library loaded for this ELF."""
|
||||
"""Dictionary of ``{path: address}`` for every library loaded for this ELF.
|
||||
|
||||
.. warning::
|
||||
|
||||
Getting this attribute actually runs the executable.
|
||||
Make sure that you trust the binary you are exploiting.
|
||||
If it adds itself as ``DT_NEEDED``, has overlapping segments,
|
||||
ambiguous headers, or employs text relocations, it can run arbitrary
|
||||
code even though you are just inspecting it.
|
||||
Running is the only reliable way to ensure all the libraries are
|
||||
loaded from the correct paths, because some of them may change
|
||||
loading logic.
|
||||
|
||||
Exploitability first noticed at CWTE CTF 2025.
|
||||
"""
|
||||
if self._libs is None:
|
||||
self._populate_libraries()
|
||||
return self._libs
|
||||
|
||||
@property
|
||||
def maps(self):
|
||||
"""Dictionary of {name: address} for every mapping in this ELF's address space."""
|
||||
"""Dictionary of ``{name: address}`` for every mapping in this ELF's address space.
|
||||
|
||||
.. warning::
|
||||
|
||||
Getting this attribute actually runs the executable.
|
||||
Make sure that you trust the binary you are exploiting
|
||||
(see :attr:`.ELF.libs`).
|
||||
"""
|
||||
if self._maps is None:
|
||||
self._populate_libraries()
|
||||
return self._maps
|
||||
|
||||
@property
|
||||
def libc(self):
|
||||
""":class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
|
||||
""":class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``/libc[.-]``,
|
||||
and we can determine the appropriate path to it on the local
|
||||
system, returns a new :class:`.ELF` object pertaining to that library.
|
||||
Prints the `checksec` output of the library if it was printed for the original ELF too.
|
||||
|
||||
If not found, the value will be :const:`None`.
|
||||
|
||||
.. warning::
|
||||
|
||||
Getting this attribute actually runs the executable.
|
||||
Make sure that you trust the binary you are exploiting
|
||||
(see :attr:`.ELF.libs`).
|
||||
"""
|
||||
for lib in self.libs:
|
||||
if '/libc.' in lib or '/libc-' in lib:
|
||||
return ELF(lib)
|
||||
if ('/libc.' in lib or '/libc-' in lib) and lib != self.path:
|
||||
return ELF(lib, self._print_checksec)
|
||||
|
||||
def _populate_libraries(self):
|
||||
"""
|
||||
@@ -816,6 +879,13 @@ class ELF(ELFFile):
|
||||
log.warn_once("Injected /proc/self/maps code did not execute correctly")
|
||||
return {}
|
||||
|
||||
# Sometimes the original binary already fail to run, for example, in case glibc mismatches.
|
||||
try:
|
||||
int(data.split('-', 1)[0], 16)
|
||||
except ValueError:
|
||||
log.warn_once("Cannot execute `%s` to get /proc/self/maps", self.path)
|
||||
return {}
|
||||
|
||||
# Swap in the original ELF name
|
||||
data = data.replace(path, self.path)
|
||||
|
||||
@@ -883,10 +953,9 @@ class ELF(ELFFile):
|
||||
continue
|
||||
|
||||
for symbol in _iter_symbols(section):
|
||||
value = symbol.entry.st_value
|
||||
if not value:
|
||||
if not symbol.name or symbol.entry.st_shndx == 'SHN_UNDEF':
|
||||
continue
|
||||
self.symbols[symbol.name] = value
|
||||
self.symbols[symbol.name] = symbol.entry.st_value
|
||||
|
||||
def _populate_synthetic_symbols(self):
|
||||
"""Adds symbols from the GOT and PLT to the symbols dictionary.
|
||||
@@ -917,15 +986,25 @@ class ELF(ELFFile):
|
||||
self.symbols['got.' + symbol] = address
|
||||
|
||||
def _populate_got(self):
|
||||
"""Loads the symbols for all relocations"""
|
||||
"""Loads the symbols for all relocations.
|
||||
|
||||
>>> libc = ELF(which('bash')).libc
|
||||
>>> assert 'strchrnul' in libc.got
|
||||
>>> assert 'memcpy' in libc.got
|
||||
>>> assert libc.got.strchrnul != libc.got.memcpy
|
||||
"""
|
||||
# Statically linked implies no relocations, since there is no linker
|
||||
# Could always be self-relocating like Android's linker *shrug*
|
||||
if self.statically_linked:
|
||||
return
|
||||
|
||||
revsymbols = defaultdict(list)
|
||||
for name, addr in self.symbols.items():
|
||||
revsymbols[addr].append(name)
|
||||
|
||||
for section in self.sections:
|
||||
# We are only interested in relocations
|
||||
if not isinstance(section, RelocationSection):
|
||||
if not isinstance(section, (RelocationSection, RelrRelocationSection)):
|
||||
continue
|
||||
|
||||
# Only get relocations which link to another section (for symbols)
|
||||
@@ -937,7 +1016,13 @@ class ELF(ELFFile):
|
||||
for rel in section.iter_relocations():
|
||||
sym_idx = rel.entry.r_info_sym
|
||||
|
||||
if not sym_idx:
|
||||
if not sym_idx and rel.is_RELA():
|
||||
# TODO: actually resolve relocations
|
||||
relocated = rel.entry.r_addend # sufficient for now
|
||||
|
||||
symnames = revsymbols[relocated]
|
||||
for symname in symnames:
|
||||
self.got[symname] = rel.entry.r_offset
|
||||
continue
|
||||
|
||||
symbol = symbols.get_symbol(sym_idx)
|
||||
@@ -1191,13 +1276,14 @@ class ELF(ELFFile):
|
||||
segments = self.executable_segments
|
||||
else:
|
||||
segments = self.segments
|
||||
|
||||
needle = packing._need_bytes(needle)
|
||||
for seg in segments:
|
||||
addr = seg.header.p_vaddr
|
||||
memsz = seg.header.p_memsz
|
||||
zeroed = memsz - seg.header.p_filesz
|
||||
filesz = seg.header.p_filesz
|
||||
zeroed = memsz - filesz
|
||||
offset = seg.header.p_offset
|
||||
data = self.mmap[offset:offset+memsz]
|
||||
data = self.mmap[offset:offset+filesz]
|
||||
data += b'\x00' * zeroed
|
||||
offset = 0
|
||||
while True:
|
||||
@@ -1304,6 +1390,7 @@ class ELF(ELFFile):
|
||||
or :const:`None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> bash = ELF(which('bash'))
|
||||
>>> bash.vaddr_to_offset(bash.address)
|
||||
0
|
||||
@@ -1454,6 +1541,7 @@ class ELF(ELFFile):
|
||||
that it stays in the same segment.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> bash = ELF(which('bash'))
|
||||
>>> bash.read(bash.address+1, 3)
|
||||
b'ELF'
|
||||
@@ -1553,7 +1641,7 @@ class ELF(ELFFile):
|
||||
dt = None
|
||||
dynamic = self.get_section_by_name('.dynamic')
|
||||
|
||||
if not dynamic:
|
||||
if not dynamic or not isinstance(dynamic, DynamicSection):
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -1736,131 +1824,136 @@ class ELF(ELFFile):
|
||||
\\* Hardware limitations are ignored.
|
||||
|
||||
If ``READ_IMPLIES_EXEC`` is set, then `all readable pages are executable`__.
|
||||
.. __: https://github.com/torvalds/linux/blob/v6.3/fs/binfmt_elf.c#L1008-L1009
|
||||
.. code-block:: c
|
||||
|
||||
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
|
||||
current->personality |= READ_IMPLIES_EXEC;
|
||||
.. __: https://github.com/torvalds/linux/blob/v6.3/fs/binfmt_elf.c#L1008-L1009
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
|
||||
current->personality |= READ_IMPLIES_EXEC;
|
||||
|
||||
.. [#x86_5.7]
|
||||
`source <https://github.com/torvalds/linux/blob/v5.7/arch/x86/include/asm/elf.h#L285-L286>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
#define elf_read_implies_exec(ex, executable_stack) \\
|
||||
(executable_stack != EXSTACK_DISABLE_X)
|
||||
#define elf_read_implies_exec(ex, executable_stack) \\
|
||||
(executable_stack != EXSTACK_DISABLE_X)
|
||||
|
||||
.. [#x86_5.8]
|
||||
`source <https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L305-L306>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
#define elf_read_implies_exec(ex, executable_stack) \\
|
||||
(mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
|
||||
#define elf_read_implies_exec(ex, executable_stack) \\
|
||||
(mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
|
||||
|
||||
`mmap_is_ia32()`__:
|
||||
.. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
|
||||
.. code-block:: c
|
||||
`mmap_is_ia32()`__:
|
||||
|
||||
/*
|
||||
* True on X86_32 or when emulating IA32 on X86_64
|
||||
*/
|
||||
static inline int mmap_is_ia32(void)
|
||||
.. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*
|
||||
* True on X86_32 or when emulating IA32 on X86_64
|
||||
*/
|
||||
static inline int mmap_is_ia32(void)
|
||||
|
||||
.. [#arm_5.7]
|
||||
`source <https://github.com/torvalds/linux/blob/v5.7/arch/arm/kernel/elf.c#L85-L92>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
int arm_elf_read_implies_exec(int executable_stack)
|
||||
{
|
||||
if (executable_stack != EXSTACK_DISABLE_X)
|
||||
return 1;
|
||||
if (cpu_architecture() < CPU_ARCH_ARMv6)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
int arm_elf_read_implies_exec(int executable_stack)
|
||||
{
|
||||
if (executable_stack != EXSTACK_DISABLE_X)
|
||||
return 1;
|
||||
if (cpu_architecture() < CPU_ARCH_ARMv6)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
.. [#arm_5.8]
|
||||
`source <https://github.com/torvalds/linux/blob/v5.8/arch/arm/kernel/elf.c#L104-L111>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
int arm_elf_read_implies_exec(int executable_stack)
|
||||
{
|
||||
if (executable_stack == EXSTACK_DEFAULT)
|
||||
return 1;
|
||||
if (cpu_architecture() < CPU_ARCH_ARMv6)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
int arm_elf_read_implies_exec(int executable_stack)
|
||||
{
|
||||
if (executable_stack == EXSTACK_DEFAULT)
|
||||
return 1;
|
||||
if (cpu_architecture() < CPU_ARCH_ARMv6)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
.. [#mips_5.17]
|
||||
`source <https://github.com/torvalds/linux/blob/v5.17/arch/mips/kernel/elf.c#L329-L342>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
|
||||
{
|
||||
if (exstack != EXSTACK_DISABLE_X) {
|
||||
/* The binary doesn't request a non-executable stack */
|
||||
return 1;
|
||||
}
|
||||
if (!cpu_has_rixi) {
|
||||
/* The CPU doesn't support non-executable memory */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
|
||||
{
|
||||
if (exstack != EXSTACK_DISABLE_X) {
|
||||
/* The binary doesn't request a non-executable stack */
|
||||
return 1;
|
||||
}
|
||||
if (!cpu_has_rixi) {
|
||||
/* The CPU doesn't support non-executable memory */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
.. [#mips_5.18]
|
||||
`source <https://github.com/torvalds/linux/blob/v5.18/arch/mips/kernel/elf.c#L329-L336>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
|
||||
{
|
||||
/*
|
||||
* Set READ_IMPLIES_EXEC only on non-NX systems that
|
||||
* do not request a specific state via PT_GNU_STACK.
|
||||
*/
|
||||
return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
|
||||
}
|
||||
int mips_elf_read_implies_exec(void *elf_ex, int exstack)
|
||||
{
|
||||
/*
|
||||
* Set READ_IMPLIES_EXEC only on non-NX systems that
|
||||
* do not request a specific state via PT_GNU_STACK.
|
||||
*/
|
||||
return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
|
||||
}
|
||||
|
||||
.. [#powerpc]
|
||||
`source <https://github.com/torvalds/linux/blob/v6.3/arch/powerpc/include/asm/elf.h#L82-L108>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
#ifdef __powerpc64__
|
||||
/* stripped */
|
||||
# define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
|
||||
(exec_stk == EXSTACK_DEFAULT) : 0)
|
||||
#else
|
||||
# define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
|
||||
#endif /* __powerpc64__ */
|
||||
#ifdef __powerpc64__
|
||||
/* stripped */
|
||||
# define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
|
||||
(exec_stk == EXSTACK_DEFAULT) : 0)
|
||||
#else
|
||||
# define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
|
||||
#endif /* __powerpc64__ */
|
||||
|
||||
.. [#ia64]
|
||||
`source <https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L203-L204>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
#define elf_read_implies_exec(ex, executable_stack) \\
|
||||
((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
|
||||
#define elf_read_implies_exec(ex, executable_stack) \\
|
||||
((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
|
||||
|
||||
EF_IA_64_LINUX_EXECUTABLE_STACK__:
|
||||
.. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
|
||||
EF_IA_64_LINUX_EXECUTABLE_STACK__:
|
||||
|
||||
.. code-block:: c
|
||||
.. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
|
||||
|
||||
#define EF_IA_64_LINUX_EXECUTABLE_STACK 0x1 /* is stack (& heap) executable by default? */
|
||||
.. code-block:: c
|
||||
|
||||
#define EF_IA_64_LINUX_EXECUTABLE_STACK 0x1 /* is stack (& heap) executable by default? */
|
||||
|
||||
.. [#the_rest]
|
||||
`source <https://github.com/torvalds/linux/blob/v6.3/include/linux/elf.h#L13>`__
|
||||
|
||||
.. code-block:: c
|
||||
.. code-block:: c
|
||||
|
||||
# define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
|
||||
# define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
|
||||
"""
|
||||
if not self.executable:
|
||||
return True
|
||||
@@ -1960,6 +2053,16 @@ class ELF(ELFFile):
|
||||
""":class:`bool`: Whether the current binary is packed with UPX."""
|
||||
return b'UPX!' in self.get_data()[:0xFF]
|
||||
|
||||
@property
|
||||
def stripped(self):
|
||||
""":class:`bool`: Whether the current binary has been stripped of symbols"""
|
||||
return not any(section['sh_type'] == 'SHT_SYMTAB' for section in self.iter_sections())
|
||||
|
||||
@property
|
||||
def debuginfo(self):
|
||||
""":class:`bool`: Whether the current binary has debug information"""
|
||||
return self.get_section_by_name('.debug_info') is not None
|
||||
|
||||
@property
|
||||
def pie(self):
|
||||
""":class:`bool`: Whether the current binary is position-independent."""
|
||||
@@ -2003,26 +2106,26 @@ class ELF(ELFFile):
|
||||
|
||||
# Kernel version?
|
||||
if self.version and self.version != (0,):
|
||||
res.append('Version:'.ljust(10) + '.'.join(map(str, self.version)))
|
||||
res.append('Version:'.ljust(12) + '.'.join(map(str, self.version)))
|
||||
if self.build:
|
||||
res.append('Build:'.ljust(10) + self.build)
|
||||
res.append('Build:'.ljust(12) + self.build)
|
||||
|
||||
res.extend([
|
||||
"RELRO:".ljust(10) + {
|
||||
"RELRO:".ljust(12) + {
|
||||
'Full': green("Full RELRO"),
|
||||
'Partial': yellow("Partial RELRO"),
|
||||
None: red("No RELRO")
|
||||
}[self.relro],
|
||||
"Stack:".ljust(10) + {
|
||||
"Stack:".ljust(12) + {
|
||||
True: green("Canary found"),
|
||||
False: red("No canary found")
|
||||
}[self.canary],
|
||||
"NX:".ljust(10) + {
|
||||
"NX:".ljust(12) + {
|
||||
True: green("NX enabled"),
|
||||
False: red("NX disabled"),
|
||||
None: yellow("NX unknown - GNU_STACK missing"),
|
||||
}[self.nx],
|
||||
"PIE:".ljust(10) + {
|
||||
"PIE:".ljust(12) + {
|
||||
True: green("PIE enabled"),
|
||||
False: red("No PIE (%#x)" % self.address)
|
||||
}[self.pie],
|
||||
@@ -2030,35 +2133,47 @@ class ELF(ELFFile):
|
||||
|
||||
# Execstack may be a thing, even with NX enabled, because of glibc
|
||||
if self.execstack and self.nx is not False:
|
||||
res.append("Stack:".ljust(10) + red("Executable"))
|
||||
res.append("Stack:".ljust(12) + red("Executable"))
|
||||
|
||||
# Are there any RWX areas in the binary?
|
||||
#
|
||||
# This will occur if NX is disabled and *any* area is
|
||||
# RW, or can expressly occur.
|
||||
if self.rwx_segments or (not self.nx and self.writable_segments):
|
||||
res += [ "RWX:".ljust(10) + red("Has RWX segments") ]
|
||||
res += [ "RWX:".ljust(12) + red("Has RWX segments") ]
|
||||
|
||||
if self.rpath:
|
||||
res += [ "RPATH:".ljust(10) + red(repr(self.rpath)) ]
|
||||
res += [ "RPATH:".ljust(12) + red(repr(self.rpath)) ]
|
||||
|
||||
if self.runpath:
|
||||
res += [ "RUNPATH:".ljust(10) + red(repr(self.runpath)) ]
|
||||
res += [ "RUNPATH:".ljust(12) + red(repr(self.runpath)) ]
|
||||
|
||||
if self.packed:
|
||||
res.append('Packer:'.ljust(10) + red("Packed with UPX"))
|
||||
res.append('Packer:'.ljust(12) + red("Packed with UPX"))
|
||||
|
||||
if self.fortify:
|
||||
res.append("FORTIFY:".ljust(10) + green("Enabled"))
|
||||
res.append("FORTIFY:".ljust(12) + green("Enabled"))
|
||||
|
||||
if self.asan:
|
||||
res.append("ASAN:".ljust(10) + green("Enabled"))
|
||||
res.append("ASAN:".ljust(12) + green("Enabled"))
|
||||
|
||||
if self.msan:
|
||||
res.append("MSAN:".ljust(10) + green("Enabled"))
|
||||
res.append("MSAN:".ljust(12) + green("Enabled"))
|
||||
|
||||
if self.ubsan:
|
||||
res.append("UBSAN:".ljust(10) + green("Enabled"))
|
||||
res.append("UBSAN:".ljust(12) + green("Enabled"))
|
||||
|
||||
if self.shadowstack:
|
||||
res.append("SHSTK:".ljust(12) + green("Enabled"))
|
||||
|
||||
if self.ibt:
|
||||
res.append("IBT:".ljust(12) + green("Enabled"))
|
||||
|
||||
if not self.stripped:
|
||||
res.append("Stripped:".ljust(12) + red("No"))
|
||||
|
||||
if self.debuginfo:
|
||||
res.append("Debuginfo:".ljust(12) + red("Yes"))
|
||||
|
||||
# Check for Linux configuration, it must contain more than
|
||||
# just the version.
|
||||
@@ -2116,6 +2231,31 @@ class ELF(ELFFile):
|
||||
""":class:`bool`: Whether the current binary was built with
|
||||
Undefined Behavior Sanitizer (``UBSAN``)."""
|
||||
return any(s.startswith('__ubsan_') for s in self.symbols)
|
||||
|
||||
@property
|
||||
def shadowstack(self):
|
||||
""":class:`bool`: Whether the current binary was built with
|
||||
Shadow Stack (``SHSTK``)"""
|
||||
if self.arch not in ['i386', 'amd64']:
|
||||
return False
|
||||
for prop in self.iter_properties():
|
||||
if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
|
||||
continue
|
||||
return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_SHSTK'] > 0
|
||||
return False
|
||||
|
||||
@property
|
||||
def ibt(self):
|
||||
""":class:`bool`: Whether the current binary was built with
|
||||
Indirect Branch Tracking (``IBT``)"""
|
||||
if self.arch not in ['i386', 'amd64']:
|
||||
return False
|
||||
for prop in self.iter_properties():
|
||||
if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
|
||||
continue
|
||||
return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_IBT'] > 0
|
||||
return False
|
||||
|
||||
|
||||
def _update_args(self, kw):
|
||||
kw.setdefault('arch', self.arch)
|
||||
@@ -2246,3 +2386,123 @@ class ELF(ELFFile):
|
||||
return
|
||||
|
||||
log.error("Could not find PT_GNU_STACK, stack should already be executable")
|
||||
|
||||
@staticmethod
|
||||
def set_runpath(exepath, runpath):
|
||||
r"""set_runpath(exepath, runpath) -> ELF
|
||||
|
||||
Patches the RUNPATH of the ELF to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
|
||||
|
||||
The dynamic loader will look for any needed shared libraries in the given path first,
|
||||
before trying the system library paths. This is useful to run a binary with a different
|
||||
libc binary.
|
||||
|
||||
Arguments:
|
||||
exepath(str): Path to the binary to patch.
|
||||
runpath(str): Path containing the needed libraries.
|
||||
|
||||
Returns:
|
||||
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
|
||||
|
||||
Example:
|
||||
|
||||
>>> tmpdir = tempfile.mkdtemp()
|
||||
>>> ls_path = os.path.join(tmpdir, 'ls')
|
||||
>>> _ = shutil.copy(which('ls'), ls_path)
|
||||
>>> e = ELF.set_runpath(ls_path, './libs')
|
||||
>>> e.runpath == b'./libs'
|
||||
True
|
||||
"""
|
||||
if not which('patchelf'):
|
||||
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
|
||||
return None
|
||||
try:
|
||||
subprocess.check_output(['patchelf', '--set-rpath', runpath, exepath], stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.failure('Patching RUNPATH failed (%d): %r', e.returncode, e.stdout)
|
||||
return ELF(exepath, checksec=False)
|
||||
|
||||
@staticmethod
|
||||
def set_interpreter(exepath, interpreter_path):
|
||||
r"""set_interpreter(exepath, interpreter_path) -> ELF
|
||||
|
||||
Patches the interpreter of the ELF to the given binary using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
|
||||
|
||||
When running the binary, the new interpreter will be used to load the ELF.
|
||||
|
||||
Arguments:
|
||||
exepath(str): Path to the binary to patch.
|
||||
interpreter_path(str): Path to the ld.so dynamic loader.
|
||||
|
||||
Returns:
|
||||
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
|
||||
|
||||
Example:
|
||||
|
||||
>>> tmpdir = tempfile.mkdtemp()
|
||||
>>> ls_path = os.path.join(tmpdir, 'ls')
|
||||
>>> _ = shutil.copy(which('ls'), ls_path)
|
||||
>>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
|
||||
>>> e.linker == b'/tmp/correct_ld.so'
|
||||
True
|
||||
"""
|
||||
# patch the interpreter
|
||||
if not which('patchelf'):
|
||||
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
|
||||
return None
|
||||
try:
|
||||
subprocess.check_output(['patchelf', '--set-interpreter', interpreter_path, exepath], stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.failure('Patching interpreter failed (%d): %r', e.returncode, e.stdout)
|
||||
return ELF(exepath, checksec=False)
|
||||
|
||||
@staticmethod
|
||||
def patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc'):
|
||||
r"""patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc') -> ELF
|
||||
|
||||
Looks for the interpreter binary in the given path and patches the binary to use
|
||||
it if available. Also patches the RUNPATH to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
|
||||
|
||||
Arguments:
|
||||
exe_path(str): Path to the binary to patch.
|
||||
custom_library_path(str): Path to a folder containing the libraries.
|
||||
create_copy(bool): Create a copy of the binary and apply the patches to the copy.
|
||||
suffix(str): Suffix to append to the filename when creating the copy to patch.
|
||||
|
||||
Returns:
|
||||
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
|
||||
|
||||
Example:
|
||||
|
||||
>>> tmpdir = tempfile.mkdtemp()
|
||||
>>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
|
||||
>>> write(linker_path, b'loader')
|
||||
>>> ls_path = os.path.join(tmpdir, 'ls')
|
||||
>>> _ = shutil.copy(which('ls'), ls_path)
|
||||
>>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
|
||||
>>> e.runpath.decode() == tmpdir
|
||||
True
|
||||
>>> e.linker.decode() == linker_path
|
||||
True
|
||||
"""
|
||||
if not which('patchelf'):
|
||||
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
|
||||
return None
|
||||
|
||||
# Create a copy of the ELF to patch instead of the original file.
|
||||
if create_copy:
|
||||
import shutil
|
||||
patched_path = exe_path + suffix
|
||||
shutil.copy2(exe_path, patched_path)
|
||||
exe_path = patched_path
|
||||
|
||||
# Set interpreter in ELF to the one in the library path.
|
||||
interpreter_name = [filename for filename in os.listdir(custom_library_path) if filename.startswith('ld-')]
|
||||
if interpreter_name:
|
||||
interpreter_path = os.path.realpath(os.path.join(custom_library_path, interpreter_name[0]))
|
||||
ELF.set_interpreter(exe_path, interpreter_path)
|
||||
else:
|
||||
log.warn("Couldn't find ld.so in library path. Interpreter not set.")
|
||||
|
||||
# Set RUNPATH to library path in order to find other libraries.
|
||||
return ELF.set_runpath(exe_path, custom_library_path)
|
||||
+1
-1
@@ -22,6 +22,6 @@ CAT_PROC_MAPS_EXIT = {
|
||||
'726f093c2f702935f0ffa9af7365093c632f2935f4ffa9af2f6d093c6c662935f8ffa9af8cff193c9e8f393727482003fcffa9aff0ffbd272020a003ffff0528ffbf192427302003a50f02340c01010122e8a603fcffa2affcffa48f2028a003a30f02340c010101feff1924272020032028a003fcffa2affcffa68fa40f02340c010101'
|
||||
'ffff0428a10f02340c010101',
|
||||
'aarch64':
|
||||
'ee058ed24eeeadf26eecc5f26eaeecf28fcd8cd2efa5adf22f0ccef26f0ee0f2ee3fbfa980f39fd2e0ffbff2e0ffdff2e0fffff2e1030091e2031faa080780d2010000d4020088d2ff6322cbe1030091e80780d2010000d4e20300aa200080d2e1030091080880d2010000d4'
|
||||
'ee058ed24eeeadf26eecc5f26eaeecf28fcd8cd2efa5adf22f0ccef26f0ee0f2ee3fbfa980f39fd2e0ffbff2e0ffdff2e0fffff2e1633f8be2031faa080780d2010000d4222080d22e2088d2c20102caff6322cbe1633f8be80780d2010000d402041f8b4f2080d26c2080d28f010fcae0030faae1633f8b080880d2010000d4'
|
||||
'e0031faaa80b80d2010000d4',
|
||||
}
|
||||
+33
-2
@@ -53,9 +53,40 @@ def emulate_plt_instructions(elf, got, address, data, targets):
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def __ensure_memory_to_run_unicorn():
|
||||
"""
|
||||
Check if there is enough memory to run Unicorn Engine.
|
||||
Unicorn Engine requires 1GB of memory to run, if there isn't enough memory it calls exit(1).
|
||||
|
||||
This is a bug in Unicorn Engine, see: https://github.com/unicorn-engine/unicorn/issues/1766
|
||||
"""
|
||||
try:
|
||||
from mmap import mmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE
|
||||
|
||||
mm = mmap(
|
||||
-1, 1024 * 1024 * 1024, MAP_PRIVATE | MAP_ANON, PROT_WRITE | PROT_READ
|
||||
)
|
||||
mm.close()
|
||||
except OSError:
|
||||
raise OSError("Cannot allocate 1GB memory to run Unicorn Engine")
|
||||
except ImportError:
|
||||
# Can only mmap files on Windows, would need to use VirtualAlloc.
|
||||
pass
|
||||
|
||||
|
||||
def prepare_unicorn_and_context(elf, got, address, data):
|
||||
import unicorn as U
|
||||
|
||||
if elf.arch in ('mips', 'mips64'):
|
||||
# Unicorn 2.1.3 and 2.1.4 have a crashing bug when trying to emulate MIPS.
|
||||
# https://github.com/unicorn-engine/unicorn/issues/2134
|
||||
if U.UC_VERSION_MAJOR == 2 and U.UC_VERSION_MINOR == 1 and U.UC_VERSION_PATCH in (3, 4):
|
||||
raise OSError("Unicorn Engine 2.1.3 and 2.1.4 have a crashing bug when emulating MIPS, please install an earlier or later version: https://github.com/unicorn-engine/unicorn/issues/2134")
|
||||
|
||||
|
||||
__ensure_memory_to_run_unicorn()
|
||||
|
||||
# Instantiate the emulator with the correct arguments for the current
|
||||
# architecutre.
|
||||
arch = {
|
||||
@@ -145,8 +176,8 @@ def emulate_plt_instructions_inner(uc, elf, got, pc, data):
|
||||
return False
|
||||
|
||||
hooks = [
|
||||
uc.hook_add(U.UC_HOOK_MEM_READ | U.UC_HOOK_MEM_READ_UNMAPPED,
|
||||
hook_mem, stopped_addr),
|
||||
uc.hook_add(U.UC_HOOK_MEM_READ, hook_mem, stopped_addr),
|
||||
uc.hook_add(U.UC_HOOK_MEM_READ_UNMAPPED, hook_mem, stopped_addr),
|
||||
]
|
||||
|
||||
# callback for tracing instructions
|
||||
|
||||
@@ -132,7 +132,7 @@ class AsciiShellcodeEncoder(Encoder):
|
||||
Examples:
|
||||
|
||||
>>> context.update(arch='i386', os='linux')
|
||||
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
>>> encoders.i386.ascii_shellcode.encode._get_allocator(300, vocab)
|
||||
bytearray(b'TX-!!!!-!_``-t~~~P\\%!!!!%@@@@')
|
||||
"""
|
||||
@@ -178,7 +178,7 @@ class AsciiShellcodeEncoder(Encoder):
|
||||
Examples:
|
||||
|
||||
>>> context.update(arch='i386', os='linux')
|
||||
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
>>> a, b = encoders.i386.ascii_shellcode.encode._find_negatives(vocab)
|
||||
>>> a & b
|
||||
0
|
||||
@@ -212,7 +212,7 @@ class AsciiShellcodeEncoder(Encoder):
|
||||
|
||||
>>> context.update(arch='i386', os='linux')
|
||||
>>> sc = bytearray(b'ABCDEFGHIGKLMNOPQRSTUVXYZ')
|
||||
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
>>> 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')
|
||||
"""
|
||||
@@ -255,7 +255,7 @@ class AsciiShellcodeEncoder(Encoder):
|
||||
Examples:
|
||||
|
||||
>>> context.update(arch='i386', os='linux')
|
||||
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
|
||||
>>> 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))
|
||||
|
||||
@@ -41,7 +41,7 @@ decoders = {
|
||||
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'\xe0\xff\xfd\x23', # addi sp,ra,-32
|
||||
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
|
||||
@@ -72,7 +72,7 @@ decoders = {
|
||||
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'\x23\xfd\xff\xe0', # addi sp,ra,-32
|
||||
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
|
||||
|
||||
+15
-3
@@ -156,6 +156,18 @@ class FileStructure(object):
|
||||
_wide_data: 0xdeadbeef
|
||||
unknown2: 0x0
|
||||
vtable: 0x0}
|
||||
|
||||
Bytes fields are padded to the correct field size.
|
||||
For example, unknown2 is 56 bytes on i386, so a short value gets
|
||||
zero-padded to 56, not to context.bytes (4):
|
||||
|
||||
>>> context.clear(arch='i386')
|
||||
>>> fileStr2 = FileStructure(null=0)
|
||||
>>> fileStr2.vtable = 0x561859f0
|
||||
>>> old_len = len(bytes(fileStr2))
|
||||
>>> fileStr2.unknown2 = b'AB'
|
||||
>>> len(bytes(fileStr2)) == old_len
|
||||
True
|
||||
"""
|
||||
|
||||
vars_=[]
|
||||
@@ -176,7 +188,7 @@ class FileStructure(object):
|
||||
def __repr__(self):
|
||||
structure=[]
|
||||
for i in self.vars_:
|
||||
structure.append(" %s: %s" % (i,hex(getattr(self, i))))
|
||||
structure.append(" %s: %#x" % (i, getattr(self, i)))
|
||||
return "{"+ "\n".join(structure)+"}"
|
||||
|
||||
def __len__(self):
|
||||
@@ -186,7 +198,7 @@ class FileStructure(object):
|
||||
structure = b''
|
||||
for val in self.vars_:
|
||||
if isinstance(getattr(self, val), bytes):
|
||||
structure += getattr(self, val).ljust(context.bytes, b'\x00')
|
||||
structure += getattr(self, val).ljust(self.length[val], b'\x00')
|
||||
else:
|
||||
if self.length[val] > 0:
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
@@ -215,7 +227,7 @@ class FileStructure(object):
|
||||
structure = b''
|
||||
for val in self.vars_:
|
||||
if isinstance(getattr(self, val), bytes):
|
||||
structure += getattr(self, val).ljust(context.bytes, b'\x00')
|
||||
structure += getattr(self, val).ljust(self.length[val], b'\x00')
|
||||
else:
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
if val == v:
|
||||
|
||||
+43
-9
@@ -129,6 +129,7 @@ def normalize_writes(writes):
|
||||
such that all values are raw bytes and consecutive writes are merged to a single key.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> context.clear(endian="little", bits=32)
|
||||
>>> normalize_writes({0x0: [p32(0xdeadbeef)], 0x4: p32(0xf00dface), 0x10: 0x41414141})
|
||||
[(0, b'\xef\xbe\xad\xde\xce\xfa\r\xf0'), (16, b'AAAA')]
|
||||
@@ -215,6 +216,7 @@ class AtomWrite(object):
|
||||
given the current format string write counter (how many bytes have been written until now).
|
||||
|
||||
Examples:
|
||||
|
||||
>>> hex(pwnlib.fmtstr.AtomWrite(0x0, 0x2, 0x2345).compute_padding(0x1111))
|
||||
'0x1234'
|
||||
>>> hex(pwnlib.fmtstr.AtomWrite(0x0, 0x2, 0xaa00).compute_padding(0xaabb))
|
||||
@@ -246,6 +248,7 @@ 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=0x77ff)
|
||||
@@ -285,11 +288,13 @@ def make_atoms_simple(address, data, badbytes=frozenset()):
|
||||
|
||||
This function is simple and does not try to minimize the number of atoms. For example, if there are no
|
||||
bad bytes, it simply returns one atom for each byte:
|
||||
|
||||
>>> pwnlib.fmtstr.make_atoms_simple(0x0, b"abc", set())
|
||||
[AtomWrite(start=0, size=1, integer=0x61, mask=0xff), AtomWrite(start=1, size=1, integer=0x62, mask=0xff), AtomWrite(start=2, size=1, integer=0x63, mask=0xff)]
|
||||
|
||||
If there are bad bytes, it will try to bypass by skipping addresses containing bad bytes, otherwise a
|
||||
RuntimeError will be raised:
|
||||
|
||||
>>> pwnlib.fmtstr.make_atoms_simple(0x61, b'abc', b'\x62')
|
||||
[AtomWrite(start=97, size=2, integer=0x6261, mask=0xffff), AtomWrite(start=99, size=1, integer=0x63, mask=0xff)]
|
||||
>>> pwnlib.fmtstr.make_atoms_simple(0x61, b'a'*0x10, b'\x62\x63\x64\x65\x66\x67\x68')
|
||||
@@ -304,14 +309,15 @@ def make_atoms_simple(address, data, badbytes=frozenset()):
|
||||
|
||||
i = 0
|
||||
out = []
|
||||
end = address + len(data)
|
||||
while i < len(data):
|
||||
candidate = AtomWrite(address + i, 1, data[i])
|
||||
while i + candidate.size < len(data) and any(x in badbytes for x in pack(candidate.end)):
|
||||
while candidate.end < end and any(x in badbytes for x in pack(candidate.end)):
|
||||
candidate = candidate.union(AtomWrite(candidate.end, 1, data[i + candidate.size]))
|
||||
|
||||
sz = min([s for s in SPECIFIER if s >= candidate.size] + [float("inf")])
|
||||
if i + sz > len(data):
|
||||
raise RuntimeError("impossible to avoid badbytes starting after offset %d (address %x)" % (i, i + address))
|
||||
if candidate.start + sz > end:
|
||||
raise RuntimeError("impossible to avoid badbytes starting after offset %d (address %#x)" % (i, i + address))
|
||||
i += candidate.size
|
||||
candidate = candidate.union(AtomWrite(candidate.end, sz - candidate.size, 0, 0))
|
||||
out.append(candidate)
|
||||
@@ -324,6 +330,7 @@ def merge_atoms_writesize(atoms, maxsize):
|
||||
This function simply merges adjacent atoms as long as the merged atom's size is not larger than ``maxsize``.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> from pwnlib.fmtstr import *
|
||||
>>> merge_atoms_writesize([AtomWrite(0, 1, 1), AtomWrite(1, 1, 1), AtomWrite(2, 1, 2)], 2)
|
||||
[AtomWrite(start=0, size=2, integer=0x101, mask=0xffff), AtomWrite(start=2, size=1, integer=0x2, mask=0xff)]
|
||||
@@ -363,6 +370,7 @@ def find_min_hamming_in_range_step(prev, step, carry, strict):
|
||||
A tuple (score, value, mask) where score equals the number of matching bytes between the returned value and target.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> initial = {(0,0): (0,0,0), (0,1): None, (1,0): None, (1,1): None}
|
||||
>>> pwnlib.fmtstr.find_min_hamming_in_range_step(initial, (0, 0xFF, 0x1), 0, 0)
|
||||
(1, 1, 255)
|
||||
@@ -418,6 +426,7 @@ def find_min_hamming_in_range(maxbytes, lower, upper, target):
|
||||
target(int): the target value that should be approximated
|
||||
|
||||
Examples:
|
||||
|
||||
>>> pp = lambda svm: (svm[0], hex(svm[1]), hex(svm[2]))
|
||||
>>> pp(pwnlib.fmtstr.find_min_hamming_in_range(1, 0x0, 0x100, 0xaa))
|
||||
(1, '0xaa', '0xff')
|
||||
@@ -469,6 +478,7 @@ def merge_atoms_overlapping(atoms, sz, szmax, numbwritten, overflows):
|
||||
overflows(int): how many extra overflows (of size sz) to tolerate to reduce the number of atoms
|
||||
|
||||
Examples:
|
||||
|
||||
>>> from pwnlib.fmtstr import *
|
||||
>>> merge_atoms_overlapping([AtomWrite(0, 1, 1), AtomWrite(1, 1, 1)], 2, 8, 0, 1)
|
||||
[AtomWrite(start=0, size=2, integer=0x101, mask=0xffff)]
|
||||
@@ -498,7 +508,7 @@ def merge_atoms_overlapping(atoms, sz, szmax, numbwritten, overflows):
|
||||
# the best write is the one which sets the largest number of target
|
||||
# bytes correctly
|
||||
candidate = AtomWrite(atom.start, 0, 0)
|
||||
best = (0, None)
|
||||
best = (atom.size, idx, atom)
|
||||
for nextidx, nextatom in enumerate(atoms[idx:], idx):
|
||||
# if there is no atom immediately following the current candidate
|
||||
# that we haven't written yet, stop
|
||||
@@ -527,6 +537,8 @@ def merge_atoms_overlapping(atoms, sz, szmax, numbwritten, overflows):
|
||||
|
||||
_, nextidx, best_candidate = best
|
||||
numbwritten_here += best_candidate.compute_padding(numbwritten_here)
|
||||
if numbwritten_here > maxwritten:
|
||||
maxwritten = numbwritten_here
|
||||
offset = 0
|
||||
|
||||
# for all atoms that we merged, check if all bytes are written already to update `done``
|
||||
@@ -554,6 +566,7 @@ def overlapping_atoms(atoms):
|
||||
Finds pairs of atoms that write to the same address.
|
||||
|
||||
Basic examples:
|
||||
|
||||
>>> from pwnlib.fmtstr import *
|
||||
>>> list(overlapping_atoms([AtomWrite(0, 2, 0), AtomWrite(2, 10, 1)])) # no overlaps
|
||||
[]
|
||||
@@ -561,6 +574,7 @@ def overlapping_atoms(atoms):
|
||||
[(AtomWrite(start=0, size=2, integer=0x0, mask=0xffff), AtomWrite(start=1, size=2, integer=0x1, mask=0xffff))]
|
||||
|
||||
When there are transitive overlaps, only the largest overlap is returned. For example:
|
||||
|
||||
>>> list(overlapping_atoms([AtomWrite(0, 3, 0), AtomWrite(1, 4, 1), AtomWrite(2, 4, 1)]))
|
||||
[(AtomWrite(start=0, size=3, integer=0x0, mask=0xffffff), AtomWrite(start=1, size=4, integer=0x1, mask=0xffffffff)), (AtomWrite(start=1, size=4, integer=0x1, mask=0xffffffff), AtomWrite(start=2, size=4, integer=0x1, mask=0xffffffff))]
|
||||
|
||||
@@ -626,6 +640,7 @@ def sort_atoms(atoms, numbwritten):
|
||||
numbwritten(int): the value at which the counter starts
|
||||
|
||||
Examples:
|
||||
|
||||
>>> from pwnlib.fmtstr import *
|
||||
>>> sort_atoms([AtomWrite(0, 1, 0xff), AtomWrite(1, 1, 0xfe)], 0) # the example described above
|
||||
[AtomWrite(start=1, size=1, integer=0xfe, mask=0xff), AtomWrite(start=0, size=1, integer=0xff, mask=0xff)]
|
||||
@@ -691,6 +706,7 @@ def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4, no_dol
|
||||
no_dollars(bool) : flag to generete the payload with or w/o $ notation
|
||||
|
||||
Examples:
|
||||
|
||||
>>> pwnlib.fmtstr.make_payload_dollar(1, [pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0xff)])
|
||||
(b'%255c%1$hhn', b'\x00\x00\x00\x00')
|
||||
'''
|
||||
@@ -799,7 +815,7 @@ def make_atoms(writes, sz, szmax, numbwritten, overflows, strategy, badbytes):
|
||||
all_atoms += atoms
|
||||
return all_atoms
|
||||
|
||||
def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset()):
|
||||
def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset(), no_dollars=False):
|
||||
"""
|
||||
Build a format string like fmtstr_payload but return the string and data separately.
|
||||
"""
|
||||
@@ -813,7 +829,7 @@ def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_ma
|
||||
szmax = WRITE_SIZE[write_size_max]
|
||||
atoms = make_atoms(writes, sz, szmax, numbwritten, overflows, strategy, badbytes)
|
||||
|
||||
return make_payload_dollar(offset, atoms, numbwritten)
|
||||
return make_payload_dollar(offset, atoms, numbwritten, no_dollars=no_dollars)
|
||||
|
||||
def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset(), offset_bytes=0, no_dollars=False):
|
||||
r"""fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') -> str
|
||||
@@ -825,6 +841,12 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
|
||||
The overflows argument is a format-string-length to output-amount tradeoff:
|
||||
Larger values for ``overflows`` produce shorter format strings that generate more output at runtime.
|
||||
|
||||
The writes argument is a dictionary with address/value pairs like ``{addr: value, addr2: value2}``.
|
||||
If the value is an ``int`` datatype, it will be automatically casted into a bytestring with the length of a ``long`` (8 bytes in 64-bit, 4 bytes in 32-bit).
|
||||
If a specific number of bytes is intended to be written (such as only a single byte, single short, or single int and not an entire long),
|
||||
then provide a bytestring like ``b'\x37\x13'`` or ``p16(0x1337)``.
|
||||
Note that the ``write_size`` argument does not determine **total** bytes written, only the size of each consecutive write.
|
||||
|
||||
Arguments:
|
||||
offset(int): the first formatter's offset you control
|
||||
writes(dict): dict with addr, value ``{addr: value, addr2: value2}``
|
||||
@@ -837,13 +859,18 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
|
||||
The payload in order to do needed writes
|
||||
|
||||
Examples:
|
||||
|
||||
>>> context.clear(arch = 'amd64')
|
||||
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
|
||||
b'%322419390c%4$llnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
>>> fmtstr_payload(1, {0x0: p32(0x1337babe)}, write_size='int')
|
||||
b'%322419390c%3$na\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
>>> 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'
|
||||
>>> 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'
|
||||
>>> fmtstr_payload(6, {0x8: 0x55d15d2004a0}, badbytes=b'\n')
|
||||
b'%1184c%14$lln%49c%15$hhn%6963c%16$hn%81c%17$hhn%8c%18$hhnaaaabaa\x08\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'
|
||||
>>> context.clear(arch = 'i386')
|
||||
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
|
||||
b'%322419390c%5$na\x00\x00\x00\x00'
|
||||
@@ -853,6 +880,8 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
|
||||
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'
|
||||
>>> fmtstr_payload(1, {0x0: 0x00000001}, write_size='byte')
|
||||
b'c%3$naaa\x00\x00\x00\x00'
|
||||
>>> fmtstr_payload(1, {0x0: b'\x01'}, write_size='byte')
|
||||
b'c%3$hhna\x00\x00\x00\x00'
|
||||
>>> 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'
|
||||
>>> fmtstr_payload(10, {0x404048 : 0xbadc0ffe, 0x40403c : 0xdeadbeef}, no_dollars=True)
|
||||
@@ -900,11 +929,12 @@ class FmtStr(object):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, execute_fmt, offset=None, padlen=0, numbwritten=0):
|
||||
def __init__(self, execute_fmt, offset=None, padlen=0, numbwritten=0, badbytes=frozenset()):
|
||||
self.execute_fmt = execute_fmt
|
||||
self.offset = offset
|
||||
self.padlen = padlen
|
||||
self.numbwritten = numbwritten
|
||||
self.badbytes = badbytes
|
||||
|
||||
if self.offset is None:
|
||||
self.offset, self.padlen = self.find_offset()
|
||||
@@ -968,7 +998,7 @@ class FmtStr(object):
|
||||
|
||||
"""
|
||||
fmtstr = randoms(self.padlen).encode()
|
||||
fmtstr += fmtstr_payload(self.offset, self.writes, numbwritten=self.padlen + self.numbwritten, write_size='byte')
|
||||
fmtstr += fmtstr_payload(self.offset, self.writes, numbwritten=self.padlen + self.numbwritten, badbytes=self.badbytes, write_size='byte')
|
||||
self.execute_fmt(fmtstr)
|
||||
self.writes = {}
|
||||
|
||||
@@ -979,7 +1009,7 @@ class FmtStr(object):
|
||||
|
||||
Arguments:
|
||||
addr(int): the address where you want to write
|
||||
data(int): the data that you want to write ``addr``
|
||||
data(int or bytes): the data that you want to write ``addr``
|
||||
|
||||
Returns:
|
||||
None
|
||||
@@ -993,6 +1023,10 @@ class FmtStr(object):
|
||||
>>> f.write(0x08040506, 0x1337babe)
|
||||
>>> f.execute_writes()
|
||||
b'%19c%16$hhn%36c%17$hhn%131c%18$hhn%4c%19$hhn\t\x05\x04\x08\x08\x05\x04\x08\x07\x05\x04\x08\x06\x05\x04\x08'
|
||||
>>> f2 = FmtStr(send_fmt_payload, offset=5)
|
||||
>>> f2.write(0x08040506, p16(0x1337))
|
||||
>>> f2.execute_writes()
|
||||
b'%19c%11$hhn%36c%12$hhnaa\x07\x05\x04\x08\x06\x05\x04\x08'
|
||||
|
||||
"""
|
||||
self.writes[addr] = data
|
||||
+270
-87
@@ -141,13 +141,10 @@ Member Documentation
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
import platform
|
||||
import psutil
|
||||
import random
|
||||
import re
|
||||
import shlex
|
||||
import six
|
||||
import six.moves
|
||||
import socket
|
||||
@@ -249,7 +246,43 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
|
||||
|
||||
return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch, api=api)
|
||||
|
||||
def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
|
||||
def _execve_script(argv, executable, env, ssh):
|
||||
"""_execve_script(argv, executable, env, ssh) -> str
|
||||
|
||||
Returns the filename of a python script that calls
|
||||
execve the specified program with the specified arguments.
|
||||
This script is suitable to call with gdbservers ``--wrapper`` option,
|
||||
so we have more control over the environment of the debugged process.
|
||||
|
||||
Arguments:
|
||||
argv(list): List of arguments to pass to the program
|
||||
executable(bytes): Path to the program to run
|
||||
env(dict): Environment variables to pass to the program
|
||||
ssh(ssh): SSH connection to use if we are debugging a remote process
|
||||
|
||||
Returns:
|
||||
The filename of the created script.
|
||||
"""
|
||||
# Make sure args are bytes not bytearray.
|
||||
argv = [bytes(arg) for arg in argv]
|
||||
executable = packing._encode(executable)
|
||||
if ssh:
|
||||
# ssh.process with run=false creates the script for us
|
||||
return ssh.process(argv, executable=executable, env=env, run=False)
|
||||
|
||||
script = misc._create_execve_script(argv=argv, executable=executable, env=env, log=log)
|
||||
script = script.strip()
|
||||
# Create a temporary file to hold the script
|
||||
tmp = tempfile.NamedTemporaryFile(mode="w+t",prefix='pwnlib-execve-', suffix='.py', delete=False)
|
||||
tmp.write(script)
|
||||
# Make script executable
|
||||
os.fchmod(tmp.fileno(), 0o755)
|
||||
log.debug("Created execve wrapper script %s:\n%s", tmp.name, script)
|
||||
|
||||
return tmp.name
|
||||
|
||||
|
||||
def _gdbserver_args(pid=None, path=None, port=0, gdbserver_args=None, args=None, which=None, env=None, python_wrapper_script=None):
|
||||
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
|
||||
|
||||
Sets up a listening gdbserver, to either connect to the specified
|
||||
@@ -258,12 +291,21 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
|
||||
Arguments:
|
||||
pid(int): Process ID to attach to
|
||||
path(str): Process to launch
|
||||
port(int): Port to use for gdbserver, default: random
|
||||
gdbserver_args(list): List of additional arguments to pass to gdbserver
|
||||
args(list): List of arguments to provide on the debugger command line
|
||||
which(callaable): Function to find the path of a binary.
|
||||
env(dict): Environment variables to pass to the program
|
||||
python_wrapper_script(str): Path to a python script to use with ``--wrapper``
|
||||
|
||||
Returns:
|
||||
A list of arguments to invoke gdbserver.
|
||||
"""
|
||||
if gdbserver_args is None:
|
||||
gdbserver_args = list()
|
||||
elif not isinstance(gdbserver_args, (list, tuple)):
|
||||
gdbserver_args = [gdbserver_args]
|
||||
|
||||
if [pid, path, args].count(None) != 2:
|
||||
log.error("Must specify exactly one of pid, path, or args")
|
||||
|
||||
@@ -287,7 +329,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
|
||||
|
||||
orig_args = args
|
||||
|
||||
gdbserver_args = [gdbserver, '--multi']
|
||||
gdbserver_args = [gdbserver, '--multi'] + gdbserver_args
|
||||
if context.aslr:
|
||||
gdbserver_args += ['--no-disable-randomization']
|
||||
else:
|
||||
@@ -296,16 +338,22 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
|
||||
if pid:
|
||||
gdbserver_args += ['--once', '--attach']
|
||||
|
||||
env_args = []
|
||||
if env is not None:
|
||||
env_args = []
|
||||
for key in tuple(env):
|
||||
# Special case for LD_ environment variables, so gdbserver
|
||||
# starts with the native libraries
|
||||
if key.startswith(b'LD_'): # LD_PRELOAD / LD_LIBRARY_PATH etc.
|
||||
env_args.append(b'%s=%s' % (key, env.pop(key)))
|
||||
else:
|
||||
env_args.append(b'%s=%s' % (key, env[key]))
|
||||
|
||||
if python_wrapper_script is not None:
|
||||
gdbserver_args += ['--wrapper', python_wrapper_script, '--']
|
||||
elif env is not None:
|
||||
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
|
||||
|
||||
gdbserver_args += ['localhost:0']
|
||||
gdbserver_args += ['localhost:%d' % port]
|
||||
gdbserver_args += args
|
||||
|
||||
return gdbserver_args
|
||||
@@ -315,7 +363,14 @@ def _gdbserver_port(gdbserver, ssh):
|
||||
|
||||
# Process /bin/bash created; pid = 14366
|
||||
# Listening on port 34816
|
||||
process_created = gdbserver.recvline()
|
||||
process_created = gdbserver.recvline(timeout=3)
|
||||
|
||||
if not process_created:
|
||||
log.error(
|
||||
'No output from gdbserver after 3 seconds. Try setting the SHELL=/bin/sh '
|
||||
'environment variable or using the env={} argument if you are affected by '
|
||||
'https://sourceware.org/bugzilla/show_bug.cgi?id=26116'
|
||||
)
|
||||
|
||||
if process_created.startswith(b'ERROR:'):
|
||||
raise ValueError(
|
||||
@@ -363,7 +418,7 @@ def _get_runner(ssh=None):
|
||||
else: return tubes.process.process
|
||||
|
||||
@LocalContext
|
||||
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=False, **kwargs):
|
||||
def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, port=0, gdbserver_args=None, sysroot=None, api=False, **kwargs):
|
||||
r"""
|
||||
Launch a GDB server with the specified command line,
|
||||
and launches GDB to attach to it.
|
||||
@@ -371,9 +426,12 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
Arguments:
|
||||
args(list): Arguments to the process, similar to :class:`.process`.
|
||||
gdbscript(str): GDB script to run.
|
||||
gdb_args(list): List of additional arguments to pass to GDB.
|
||||
exe(str): Path to the executable on disk
|
||||
env(dict): Environment to start the binary in
|
||||
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
|
||||
port(int): Gdb port to use, default: random
|
||||
gdbserver_args(list): List of additional arguments to pass to gdbserver
|
||||
sysroot(str): Set an alternate system root. The system root is used to
|
||||
load absolute shared library symbol files. This is useful to instruct
|
||||
gdb to load a local version of binaries/libraries instead of downloading
|
||||
@@ -458,33 +516,27 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
|
||||
>>> io.interactive() # doctest: +SKIP
|
||||
>>> io.close()
|
||||
|
||||
Start a new process with modified argv[0]
|
||||
|
||||
Using GDB Python API:
|
||||
|
||||
.. doctest
|
||||
:skipif: six.PY2
|
||||
|
||||
Debug a new process
|
||||
|
||||
>>> io = gdb.debug(['echo', 'foo'], api=True)
|
||||
|
||||
Stop at 'write'
|
||||
|
||||
>>> bp = io.gdb.Breakpoint('write', temporary=True)
|
||||
>>> io.gdb.continue_and_wait()
|
||||
|
||||
Dump 'count'
|
||||
|
||||
>>> count = io.gdb.parse_and_eval('$rdx')
|
||||
>>> long = io.gdb.lookup_type('long')
|
||||
>>> int(count.cast(long))
|
||||
4
|
||||
|
||||
Resume the program
|
||||
|
||||
>>> io.gdb.continue_nowait()
|
||||
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh")
|
||||
>>> io.sendline(b"echo $0")
|
||||
>>> io.recvline()
|
||||
b'foo\n'
|
||||
b'\xde\xad\xbe\xef\n'
|
||||
>>> io.close()
|
||||
|
||||
Demonstrate that LD_PRELOAD is respected
|
||||
|
||||
>>> io = process(["grep", "libc.so.6", "/proc/self/maps"])
|
||||
>>> real_libc_path = io.recvline().split()[-1]
|
||||
>>> io.close()
|
||||
>>> import shutil
|
||||
>>> local_path = shutil.copy(real_libc_path, "./local-libc.so") # make a copy of libc to demonstrate that it is loaded
|
||||
>>> io = gdb.debug(["grep", "local-libc.so", "/proc/self/maps"], gdbscript="continue", env={"LD_PRELOAD": "./local-libc.so"})
|
||||
>>> io.recvline().split()[-1] # doctest: +ELLIPSIS
|
||||
b'.../local-libc.so'
|
||||
>>> io.close()
|
||||
>>> os.remove("./local-libc.so") # cleanup
|
||||
|
||||
|
||||
Using SSH:
|
||||
@@ -507,8 +559,71 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
>>> io.sendline(b"echo hello")
|
||||
|
||||
Interact with the process
|
||||
|
||||
>>> io.interactive() # doctest: +SKIP
|
||||
>>> io.close()
|
||||
|
||||
Using a modified args[0] on a remote process
|
||||
|
||||
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh", ssh=shell)
|
||||
>>> io.sendline(b"echo $0")
|
||||
>>> io.recvline()
|
||||
b'$ \xde\xad\xbe\xef\n'
|
||||
>>> io.close()
|
||||
|
||||
Using an empty args[0] on a remote process
|
||||
|
||||
>>> io = gdb.debug(args=[], gdbscript='continue', exe="/bin/sh", ssh=shell)
|
||||
>>> io.sendline(b"echo $0")
|
||||
>>> io.recvline()
|
||||
b'$ \n'
|
||||
>>> io.close()
|
||||
|
||||
|
||||
Using GDB Python API:
|
||||
|
||||
.. doctest::
|
||||
:skipif: is_python2
|
||||
|
||||
Debug a new process
|
||||
|
||||
>>> io = gdb.debug(['echo', 'foo'], api=True)
|
||||
|
||||
or using ssh
|
||||
|
||||
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
|
||||
>>> ssh_io = gdb.debug(['/bin/echo', 'foo'], ssh=shell, api=True)
|
||||
|
||||
Stop at 'write'
|
||||
|
||||
>>> bp = io.gdb.Breakpoint('write', temporary=True)
|
||||
>>> io.gdb.continue_and_wait()
|
||||
>>> ssh_bp = ssh_io.gdb.Breakpoint('write', temporary=True)
|
||||
>>> ssh_io.gdb.continue_and_wait()
|
||||
|
||||
Dump 'count'
|
||||
|
||||
>>> count = io.gdb.parse_and_eval('$rdx')
|
||||
>>> long = io.gdb.lookup_type('long')
|
||||
>>> int(count.cast(long))
|
||||
4
|
||||
>>> count = ssh_io.gdb.parse_and_eval('$rdx')
|
||||
>>> long = ssh_io.gdb.lookup_type('long')
|
||||
>>> int(count.cast(long))
|
||||
4
|
||||
|
||||
Resume the program
|
||||
|
||||
>>> io.gdb.continue_nowait()
|
||||
>>> io.recvline()
|
||||
b'foo\n'
|
||||
>>> io.close()
|
||||
|
||||
>>> ssh_io.gdb.continue_nowait()
|
||||
>>> ssh_io.recvline()
|
||||
b'foo\n'
|
||||
>>> ssh_io.close()
|
||||
>>> shell.close()
|
||||
"""
|
||||
if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
|
||||
log.error("Use gdb.attach() to debug a running process")
|
||||
@@ -522,21 +637,34 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
which = _get_which(ssh)
|
||||
gdbscript = gdbscript or ''
|
||||
|
||||
if api and runner is not tubes.process.process:
|
||||
raise ValueError('GDB Python API is supported only for local processes')
|
||||
if api and runner is not tubes.process.process and not ssh:
|
||||
raise ValueError('GDB Python API is supported only for local and ssh processes')
|
||||
|
||||
args, env = misc.normalize_argv_env(args, env, log)
|
||||
if env:
|
||||
env = {bytes(k): bytes(v) for k, v in env}
|
||||
|
||||
exe = which(packing._decode(exe or args[0]))
|
||||
if not exe:
|
||||
log.error("Could not find executable %r" % exe)
|
||||
|
||||
if context.noptrace:
|
||||
log.warn_once("Skipping debugger since context.noptrace==True")
|
||||
return runner(args, executable=exe, env=env)
|
||||
|
||||
if ssh or context.native or (context.os == 'android'):
|
||||
args = _gdbserver_args(args=args, which=which, env=env)
|
||||
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe):
|
||||
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env)
|
||||
|
||||
else:
|
||||
# GDBServer is limited in it's ability to manipulate argv[0]
|
||||
# but can use the ``--wrapper`` option to execute commands and catches
|
||||
# ``execve`` calls.
|
||||
# Therefore, we use a wrapper script to execute the target binary
|
||||
script = _execve_script(args, executable=exe, env=env, ssh=ssh)
|
||||
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env, python_wrapper_script=script)
|
||||
else:
|
||||
qemu_port = random.randint(1024, 65535)
|
||||
qemu_port = port if port != 0 else random.randint(1024, 65535)
|
||||
qemu_user = qemu.user_path()
|
||||
sysroot = sysroot or qemu.ld_prefix(env=env)
|
||||
if not qemu_user:
|
||||
@@ -557,10 +685,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
if not which(args[0]):
|
||||
log.error("%s is not installed" % args[0])
|
||||
|
||||
if not ssh:
|
||||
exe = exe or which(orig_args[0])
|
||||
if not (exe and os.path.exists(exe)):
|
||||
log.error("%s does not exist" % exe)
|
||||
|
||||
# Start gdbserver/qemu
|
||||
# (Note: We override ASLR here for the gdbserver process itself.)
|
||||
@@ -569,9 +693,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
# Set the .executable on the process object.
|
||||
gdbserver.executable = exe
|
||||
|
||||
# Find what port we need to connect to
|
||||
if ssh or context.native or (context.os == 'android'):
|
||||
port = _gdbserver_port(gdbserver, ssh)
|
||||
gdb_port = _gdbserver_port(gdbserver, ssh)
|
||||
if port != 0 and port != gdb_port:
|
||||
log.error("gdbserver port (%d) doesn't equals set port (%d)" % (gdb_port, port))
|
||||
port = gdb_port
|
||||
else:
|
||||
port = qemu_port
|
||||
|
||||
@@ -579,16 +705,20 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
|
||||
if not ssh and context.os == 'android':
|
||||
host = context.adb_host
|
||||
|
||||
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot, api=api)
|
||||
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, gdb_args=gdb_args, ssh=ssh, sysroot=sysroot, api=api)
|
||||
if api:
|
||||
_, gdb = tmp
|
||||
gdbserver.gdb = gdb
|
||||
|
||||
# gdbserver outputs a message when a client connects
|
||||
garbage = gdbserver.recvline(timeout=1)
|
||||
|
||||
# Some versions of gdbserver output an additional message
|
||||
garbage2 = gdbserver.recvline_startswith(b"Remote debugging from host ", timeout=2)
|
||||
message = b"Remote debugging from host "
|
||||
if not garbage.startswith(message):
|
||||
try:
|
||||
garbage2 = gdbserver.recvline_startswith(message, timeout=2)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
return gdbserver
|
||||
|
||||
@@ -615,6 +745,12 @@ def binary():
|
||||
>>> gdb.binary() # doctest: +SKIP
|
||||
'/usr/bin/gdb'
|
||||
"""
|
||||
if context.gdb_binary:
|
||||
gdb = misc.which(context.gdb_binary)
|
||||
if not gdb:
|
||||
log.warn_once('Path to gdb binary `{}` not found'.format(context.gdb_binary))
|
||||
return gdb
|
||||
|
||||
gdb = misc.which('pwntools-gdb') or misc.which('gdb')
|
||||
|
||||
if not context.native:
|
||||
@@ -645,7 +781,10 @@ class Breakpoint:
|
||||
"""
|
||||
# Creates a real breakpoint and connects it with this mirror
|
||||
self.conn = conn
|
||||
self.server_breakpoint = conn.root.set_breakpoint(
|
||||
self.server_breakpoint = self._server_set_breakpoint(*args, **kwargs)
|
||||
|
||||
def _server_set_breakpoint(self, *args, **kwargs):
|
||||
return self.conn.root.set_breakpoint(
|
||||
self, hasattr(self, 'stop'), *args, **kwargs)
|
||||
|
||||
def __getattr__(self, item):
|
||||
@@ -663,25 +802,43 @@ class Breakpoint:
|
||||
raise AttributeError()
|
||||
return getattr(self.server_breakpoint, item)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""Set attributes of the real breakpoint."""
|
||||
if name in (
|
||||
'enabled',
|
||||
'silent',
|
||||
'thread',
|
||||
'task',
|
||||
'ignore_count',
|
||||
'hit_count'
|
||||
'condition',
|
||||
'commands',
|
||||
):
|
||||
return setattr(self.server_breakpoint, name, value)
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
def exposed_stop(self):
|
||||
# Handle stop() call from the server.
|
||||
return self.stop()
|
||||
|
||||
class FinishBreakpoint:
|
||||
class FinishBreakpoint(Breakpoint):
|
||||
"""Mirror of ``gdb.FinishBreakpoint`` class.
|
||||
|
||||
See https://sourceware.org/gdb/onlinedocs/gdb/Finish-Breakpoints-in-Python.html
|
||||
for more information.
|
||||
"""
|
||||
|
||||
def __init__(self, conn, *args, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Do not create instances of this class directly.
|
||||
|
||||
Use ``pwnlib.gdb.Gdb.FinishBreakpoint`` instead.
|
||||
"""
|
||||
# Creates a real finish breakpoint and connects it with this mirror
|
||||
self.conn = conn
|
||||
self.server_breakpoint = conn.root.set_finish_breakpoint(
|
||||
# See https://github.com/pylint-dev/pylint/issues/4228
|
||||
# pylint: disable=useless-super-delegation
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _server_set_breakpoint(self, *args, **kwargs):
|
||||
return self.conn.root.set_finish_breakpoint(
|
||||
self, hasattr(self, 'stop'), hasattr(self, 'out_of_scope'),
|
||||
*args, **kwargs)
|
||||
|
||||
@@ -701,10 +858,6 @@ class FinishBreakpoint:
|
||||
raise AttributeError()
|
||||
return getattr(self.server_breakpoint, item)
|
||||
|
||||
def exposed_stop(self):
|
||||
# Handle stop() call from the server.
|
||||
return self.stop()
|
||||
|
||||
def exposed_out_of_scope(self):
|
||||
# Handle out_of_scope() call from the server.
|
||||
return self.out_of_scope()
|
||||
@@ -798,6 +951,8 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
Process name. The youngest process is selected.
|
||||
:obj:`tuple`
|
||||
Host, port pair of a listening ``gdbserver``
|
||||
Tries to look up the target exe from the ``gdbserver`` commandline,
|
||||
requires explicit ``exe`` argument if the target exe is not in the commandline.
|
||||
:class:`.process`
|
||||
Process to connect to
|
||||
:class:`.sock`
|
||||
@@ -834,8 +989,8 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
|
||||
Using GDB Python API:
|
||||
|
||||
.. doctest
|
||||
:skipif: six.PY2
|
||||
.. doctest::
|
||||
:skipif: is_python2
|
||||
|
||||
>>> io = process('bash')
|
||||
|
||||
@@ -882,6 +1037,30 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
>>> io.sendline(b'echo Hello from bash && exit')
|
||||
>>> io.recvall()
|
||||
b'Hello from bash\n'
|
||||
>>> server.close()
|
||||
|
||||
Attach to a gdbserver / gdbstub running on the local machine
|
||||
by specifying the host and port tuple it is listening on.
|
||||
(gdbserver always listens on 0.0.0.0)
|
||||
|
||||
>>> gdbserver = process(['gdbserver', '1.2.3.4:12345', '/bin/bash'])
|
||||
>>> gdbserver.recvline_contains(b'Listening on port', timeout=10)
|
||||
b'Listening on port 12345'
|
||||
>>> pid = gdb.attach(('0.0.0.0', 12345), gdbscript='''
|
||||
... tbreak main
|
||||
... commands
|
||||
... call puts("Hello from gdbserver debugger!")
|
||||
... continue
|
||||
... end
|
||||
... ''')
|
||||
>>> gdbserver.recvline(timeout=10) # doctest: +ELLIPSIS
|
||||
b'Remote debugging from host 127.0.0.1, ...\n'
|
||||
>>> gdbserver.recvline(timeout=10)
|
||||
b'Hello from gdbserver debugger!\n'
|
||||
>>> gdbserver.sendline(b'echo Hello from bash && exit')
|
||||
>>> gdbserver.recvline(timeout=10)
|
||||
b'Hello from bash\n'
|
||||
>>> gdbserver.close()
|
||||
|
||||
Attach to processes running on a remote machine via an SSH :class:`.ssh` process
|
||||
|
||||
@@ -1051,7 +1230,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
cmd += [exe]
|
||||
|
||||
if pid and not context.os == 'android':
|
||||
cmd += [str(pid)]
|
||||
cmd += ['-p', str(pid)]
|
||||
|
||||
if context.os == 'android' and pid:
|
||||
runner = _get_runner()
|
||||
@@ -1080,13 +1259,12 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
gdbscript = pre + (gdbscript or '')
|
||||
|
||||
if gdbscript:
|
||||
tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
|
||||
delete = False, mode = 'w+')
|
||||
log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
|
||||
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
|
||||
with tempfile.NamedTemporaryFile(prefix = 'pwnlib-gdbscript-', suffix = '.gdb',
|
||||
delete = False, mode = 'w+') as tmp:
|
||||
log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
|
||||
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
|
||||
|
||||
tmp.write(gdbscript)
|
||||
tmp.close()
|
||||
tmp.write(gdbscript)
|
||||
cmd += ['-x', tmp.name]
|
||||
|
||||
log.info('running in new terminal: %s', cmd)
|
||||
@@ -1100,7 +1278,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
gdb_pid = misc.run_in_new_terminal(cmd, preexec_fn = preexec_fn)
|
||||
|
||||
if pid and context.native:
|
||||
proc.wait_for_debugger(pid, gdb_pid)
|
||||
gdb_pid = proc.wait_for_debugger(pid, gdb_pid)
|
||||
|
||||
if not api:
|
||||
return gdb_pid
|
||||
@@ -1124,7 +1302,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
else:
|
||||
# Check to see if RPyC is installed at all in GDB
|
||||
rpyc_check = [gdb_binary, '--nx', '-batch', '-ex',
|
||||
'python import rpyc; import sys; sys.exit(123)']
|
||||
'python import rpyc; import gdb; gdb.execute("quit 123")']
|
||||
|
||||
if 123 != tubes.process.process(rpyc_check).poll(block=True):
|
||||
log.error('Failed to connect to GDB: rpyc is not installed')
|
||||
@@ -1343,28 +1521,33 @@ def corefile(process):
|
||||
'contain all of the data in the process.\n'
|
||||
'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
|
||||
|
||||
# This is effectively the same as what the 'gcore' binary does
|
||||
gdb_args = ['-batch',
|
||||
'-q',
|
||||
'-nx',
|
||||
'-ex', 'set pagination off',
|
||||
'-ex', 'set height 0',
|
||||
'-ex', 'set width 0',
|
||||
'-ex', 'set use-coredump-filter on',
|
||||
'-ex', 'generate-core-file %s' % corefile_path,
|
||||
'-ex', 'detach']
|
||||
# This is effectively the same as what the 'gcore' binary does.
|
||||
# Run GDB directly instead of going through attach(), which has to
|
||||
# race GDB to observe the tracer before it already detached again.
|
||||
cmd = [binary(),
|
||||
'-batch',
|
||||
'-q',
|
||||
'-nx',
|
||||
'-ex', 'set pagination off',
|
||||
'-ex', 'set height 0',
|
||||
'-ex', 'set width 0',
|
||||
'-ex', 'set use-coredump-filter on',
|
||||
'-ex', 'attach %d' % process.pid,
|
||||
'-ex', 'generate-core-file %s' % corefile_path,
|
||||
'-ex', 'detach']
|
||||
|
||||
with context.local(terminal = ['sh', '-c']):
|
||||
with context.quiet:
|
||||
pid = attach(process, gdb_args=gdb_args)
|
||||
log.debug("Got GDB pid %d", pid)
|
||||
try:
|
||||
psutil.Process(pid).wait()
|
||||
except psutil.Error:
|
||||
pass
|
||||
with context.quiet:
|
||||
gdb = tubes.process.process(cmd, stdout=tubes.process.PIPE)
|
||||
gdb.wait_for_close(timeout=60)
|
||||
timed_out = gdb.poll() is None
|
||||
output = gdb.recvallS(timeout=0)
|
||||
|
||||
if timed_out:
|
||||
log.error("GDB did not finish dumping process %d within 60 seconds", process.pid)
|
||||
|
||||
if not os.path.exists(corefile_path):
|
||||
log.error("Could not generate a corefile for process %d", process.pid)
|
||||
log.error("Could not generate a corefile for process %d (maybe check /proc/sys/kernel/yama/ptrace_scope)\n%s",
|
||||
process.pid, output)
|
||||
|
||||
return elf.corefile.Core(corefile_path)
|
||||
|
||||
|
||||
@@ -110,5 +110,6 @@ spawn(ThreadedServer(
|
||||
socket_path=socket_path,
|
||||
protocol_config={
|
||||
'allow_all_attrs': True,
|
||||
'allow_setattr': True,
|
||||
},
|
||||
).start)
|
||||
+1
-1
@@ -95,7 +95,7 @@ class PwntoolsLexer(RegexLexer):
|
||||
'whitespace': [
|
||||
(r'\n', Text),
|
||||
(r'\s+', Text),
|
||||
(r'/\*.*?\*/', Comment),
|
||||
(r'(?ms)/\*.*?\*/', Comment),
|
||||
(r';.*$', Comment)
|
||||
],
|
||||
'punctuation': [
|
||||
|
||||
+624
-127
@@ -4,28 +4,70 @@ Fetch a LIBC binary based on some heuristics.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import codecs
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import six
|
||||
import tempfile
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from pwnlib.context import context
|
||||
from pwnlib.elf import ELF
|
||||
from pwnlib.filesystem.path import Path
|
||||
from pwnlib.log import getLogger
|
||||
from pwnlib.tubes.process import process
|
||||
from pwnlib.util.fiddling import b64d
|
||||
from pwnlib.util.fiddling import enhex
|
||||
from pwnlib.util.fiddling import hexdump
|
||||
from pwnlib.util.fiddling import enhex, unhex
|
||||
from pwnlib.util.hashes import sha1filehex, sha256filehex, md5filehex
|
||||
from pwnlib.util.misc import read
|
||||
from pwnlib.util.misc import which
|
||||
from pwnlib.util.misc import write
|
||||
from pwnlib.util.safeeval import const
|
||||
from pwnlib.util.web import wget
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
HASHES = ['build_id', 'sha1', 'sha256', 'md5']
|
||||
|
||||
def _turbofast_extract_build_id(path):
|
||||
"""
|
||||
Elf_External_Note:
|
||||
|
||||
0x00 +--------+
|
||||
| namesz | <- Size of entry's owner string
|
||||
0x04 +--------+
|
||||
| descsz | <- Size of the note descriptor
|
||||
0x08 +--------+
|
||||
| type | <- Interpretation of the descriptor
|
||||
0x0c +--------+
|
||||
| name | <- Start of the name+desc data
|
||||
... +--------
|
||||
| desc |
|
||||
... +--------+
|
||||
"""
|
||||
data = read(path, 0x1000)
|
||||
# search NT_GNU_BUILD_ID and b"GNU\x00" (type+name)
|
||||
idx = data.find(unhex("03000000474e5500"))
|
||||
if idx == -1:
|
||||
return enhex(ELF(path, checksec=False).buildid or b'')
|
||||
descsz, = struct.unpack("<L", data[idx-4: idx])
|
||||
return enhex(data[idx+8: idx+8+descsz])
|
||||
|
||||
|
||||
TYPES = {
|
||||
'libs_id': None,
|
||||
'build_id': _turbofast_extract_build_id,
|
||||
'sha1': sha1filehex,
|
||||
'sha256': sha256filehex,
|
||||
'md5': md5filehex,
|
||||
}
|
||||
|
||||
# mapping for search result (same as libc.rip)
|
||||
MAP_TYPES = {
|
||||
'libs_id': 'id',
|
||||
'build_id': 'buildid'
|
||||
}
|
||||
|
||||
DEBUGINFOD_SERVERS = [
|
||||
'https://debuginfod.ubuntu.com/',
|
||||
'https://debuginfod.debian.net/',
|
||||
'https://debuginfod.elfutils.org/',
|
||||
]
|
||||
|
||||
@@ -33,15 +75,25 @@ if 'DEBUGINFOD_URLS' in os.environ:
|
||||
urls = os.environ['DEBUGINFOD_URLS'].split(' ')
|
||||
DEBUGINFOD_SERVERS = urls + DEBUGINFOD_SERVERS
|
||||
|
||||
# Allow to override url with a caching proxy in CI
|
||||
LIBC_RIP_URL = os.environ.get("PWN_LIBCRIP_URL", "https://libc.rip").rstrip("/")
|
||||
GITLAB_LIBCDB_URL = os.environ.get("PWN_GITLAB_LIBCDB_URL", "https://gitlab.com").rstrip("/")
|
||||
|
||||
# Retry failed lookups after some time
|
||||
NEGATIVE_CACHE_EXPIRY = 60 * 60 * 24 * 7 # 1 week
|
||||
|
||||
# https://gitlab.com/libcdb/libcdb wasn't updated after 2019,
|
||||
# but still is a massive database of older libc binaries.
|
||||
def provider_libcdb(hex_encoded_id, hash_type):
|
||||
def provider_libcdb(hex_encoded_id, search_type):
|
||||
if search_type == 'libs_id':
|
||||
return None
|
||||
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
from six.moves import urllib
|
||||
|
||||
# Build the URL using the requested hash type
|
||||
url_base = "https://gitlab.com/libcdb/libcdb/raw/master/hashes/%s/" % hash_type
|
||||
url_base = "{}/libcdb/libcdb/raw/master/hashes/{}/".format(GITLAB_LIBCDB_URL, search_type)
|
||||
url = urllib.parse.urljoin(url_base, hex_encoded_id)
|
||||
|
||||
data = b""
|
||||
@@ -51,7 +103,7 @@ def provider_libcdb(hex_encoded_id, hash_type):
|
||||
data = wget(url, timeout=20)
|
||||
|
||||
if not data:
|
||||
log.warn_once("Could not fetch libc for %s %s from libcdb", hash_type, hex_encoded_id)
|
||||
log.warn_once("Could not fetch libc for %s %s from libcdb", search_type, hex_encoded_id)
|
||||
break
|
||||
|
||||
# GitLab serves up symlinks with
|
||||
@@ -59,63 +111,177 @@ def provider_libcdb(hex_encoded_id, hash_type):
|
||||
url = os.path.dirname(url) + '/'
|
||||
url = urllib.parse.urljoin(url.encode('utf-8'), data)
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc for %s %s from libcdb: %s", hash_type, hex_encoded_id, e)
|
||||
log.warn_once("Failed to fetch libc for %s %s from libcdb: %s", search_type, hex_encoded_id, e)
|
||||
return data
|
||||
|
||||
# https://libc.rip/
|
||||
def provider_libc_rip(hex_encoded_id, hash_type):
|
||||
def query_libc_rip(params):
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
|
||||
# Build the request for the hash type
|
||||
# https://github.com/niklasb/libc-database/blob/master/searchengine/api.yml
|
||||
if hash_type == 'build_id':
|
||||
hash_type = 'buildid'
|
||||
url = "https://libc.rip/api/find"
|
||||
params = {hash_type: hex_encoded_id}
|
||||
|
||||
data = b""
|
||||
url = "{}/api/find".format(LIBC_RIP_URL)
|
||||
try:
|
||||
result = requests.post(url, json=params, timeout=20)
|
||||
result.raise_for_status()
|
||||
libc_match = result.json()
|
||||
if not libc_match:
|
||||
log.warn_once("Could not find libc for %s %s on libc.rip", hash_type, hex_encoded_id)
|
||||
return None
|
||||
|
||||
if len(libc_match) > 1:
|
||||
log.debug("Received multiple matches. Choosing the first match and discarding the others.")
|
||||
log.debug("%r", libc_match)
|
||||
|
||||
url = libc_match[0]['download_url']
|
||||
log.debug("Downloading data from libc.rip: %s", url)
|
||||
data = wget(url, timeout=20)
|
||||
|
||||
if not data:
|
||||
log.warn_once("Could not fetch libc for %s %s from libc.rip", hash_type, hex_encoded_id)
|
||||
if result.status_code != 200:
|
||||
log.debug("Error: %s", result.text)
|
||||
return None
|
||||
return result.json()
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc for %s %s from libc.rip: %s", hash_type, hex_encoded_id, e)
|
||||
log.warn_once("Failed to fetch libc info from libc.rip: %s", e)
|
||||
return None
|
||||
|
||||
# https://libc.rip/
|
||||
def provider_libc_rip(search_target, search_type):
|
||||
# Build the request for the hash type
|
||||
# https://github.com/niklasb/libc-database/blob/master/searchengine/api.yml
|
||||
if search_type in MAP_TYPES.keys():
|
||||
search_type = MAP_TYPES[search_type]
|
||||
|
||||
params = {search_type: search_target}
|
||||
|
||||
libc_match = query_libc_rip(params)
|
||||
if not libc_match:
|
||||
log.warn_once("Could not find libc info for %s %s on libc.rip", search_type, search_target)
|
||||
return None
|
||||
|
||||
if len(libc_match) > 1:
|
||||
log.debug("Received multiple matches. Choosing the first match and discarding the others.")
|
||||
log.debug("%r", libc_match)
|
||||
|
||||
url = libc_match[0]['download_url']
|
||||
log.debug("Downloading data from libc.rip: %s", url)
|
||||
url = url.replace("https://libc.rip", LIBC_RIP_URL)
|
||||
data = wget(url, timeout=20)
|
||||
|
||||
if not data:
|
||||
log.warn_once("Could not fetch libc binary for %s %s from libc.rip", search_type, search_target)
|
||||
return None
|
||||
return data
|
||||
|
||||
PROVIDERS = [provider_libcdb, provider_libc_rip]
|
||||
# Check if the local system libc matches the requested hash.
|
||||
def provider_local_system(hex_encoded_id, search_type):
|
||||
if search_type == 'libs_id':
|
||||
return None
|
||||
shell_path = os.environ.get('SHELL', None) or '/bin/sh'
|
||||
if not os.path.exists(shell_path):
|
||||
log.debug('Shell path %r does not exist. Skipping local system libc matching.', shell_path)
|
||||
return None
|
||||
local_libc = ELF(shell_path, checksec=False).libc
|
||||
if not local_libc:
|
||||
log.debug('Cannot lookup libc from shell %r. Skipping local system libc matching.', shell_path)
|
||||
return None
|
||||
if TYPES[search_type](local_libc.path) == hex_encoded_id:
|
||||
return local_libc.data
|
||||
return None
|
||||
|
||||
def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
|
||||
assert hash_type in HASHES, hash_type
|
||||
# Offline search https://github.com/niklasb/libc-database for hash type
|
||||
def provider_local_database(search_target, search_type):
|
||||
if not context.local_libcdb:
|
||||
return None
|
||||
|
||||
localdb = Path(context.local_libcdb)
|
||||
if not localdb.is_dir():
|
||||
return None
|
||||
|
||||
# Handle the specific search type 'libs_id'
|
||||
if search_type == 'libs_id':
|
||||
libc_list = list(localdb.rglob("%s.so" % search_target))
|
||||
if len(libc_list) == 0:
|
||||
return None
|
||||
return read(libc_list[0])
|
||||
|
||||
log.debug("Searching local libc database, %s: %s", search_type, search_target)
|
||||
for libc_path in localdb.rglob("*.so"):
|
||||
if search_target == TYPES[search_type](libc_path):
|
||||
return read(libc_path)
|
||||
|
||||
return None
|
||||
|
||||
def query_local_database(params):
|
||||
if not context.local_libcdb or not params.get("symbols"):
|
||||
return None
|
||||
|
||||
localdb = Path(context.local_libcdb)
|
||||
if not localdb.is_dir():
|
||||
return None
|
||||
|
||||
res = []
|
||||
query_syms = params["symbols"]
|
||||
|
||||
# Loop through each '.symbols' file in the local database
|
||||
# Make sure `Path.rglod` order stable
|
||||
for symbol_file in sorted(localdb.rglob("*.symbols"), key=lambda x: x.as_posix()):
|
||||
libc_syms = _parse_libc_symbol(symbol_file)
|
||||
|
||||
matched = 0
|
||||
for name, addr in query_syms.items():
|
||||
if isinstance(addr, str):
|
||||
addr = int(addr, 16)
|
||||
|
||||
# Compare last 12 bits
|
||||
if libc_syms.get(name) and (libc_syms.get(name) & 0xfff) == (addr & 0xfff):
|
||||
matched += 1
|
||||
else:
|
||||
# aborting this loop once there was a mismatch.
|
||||
break
|
||||
|
||||
# Check if all symbols have been matched
|
||||
if matched == len(query_syms):
|
||||
libs_id = symbol_file.stem
|
||||
libc_path = symbol_file.parent / ("%s.so" % libs_id)
|
||||
libs_url = read(symbol_file.parent / ("%s.url" % libs_id)).decode().strip()
|
||||
res.append(_pack_libs_info(libc_path, libs_id, libs_url, libc_syms))
|
||||
|
||||
return res
|
||||
|
||||
PROVIDERS = {
|
||||
"offline": [provider_local_system, provider_local_database],
|
||||
"online": [provider_libcdb, provider_libc_rip]
|
||||
}
|
||||
|
||||
def search_by_hash(search_target, search_type='build_id', unstrip=True, offline_only=False):
|
||||
"""search_by_hash(str, str, bool, bool) -> str
|
||||
Arguments:
|
||||
search_target(str):
|
||||
Use for searching the libc. This could be a hex encoded ID (`hex_encoded_id`) or a library
|
||||
name (`libs_id`). Depending on `search_type`, this can represent different types of encoded
|
||||
values or names.
|
||||
search_type(str):
|
||||
The type of the search to be performed, it should be one of the keys in the `TYPES` dictionary.
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
If True, restricts the search to offline providers only (local database). If False, it will also
|
||||
search online providers. Default is False.
|
||||
|
||||
Returns:
|
||||
The path to the cached directory containing the downloaded libraries.
|
||||
"""
|
||||
assert search_type in TYPES, search_type
|
||||
|
||||
# Ensure that the libcdb cache directory exists
|
||||
cache, cache_valid = _check_elf_cache('libcdb', hex_encoded_id, hash_type)
|
||||
cache, cache_valid = _check_elf_cache('libcdb', search_target, search_type)
|
||||
if cache_valid:
|
||||
if unstrip:
|
||||
unstrip_libc(cache)
|
||||
return cache
|
||||
|
||||
# We searched for this buildid before, but didn't find anything.
|
||||
if cache is None:
|
||||
return None
|
||||
|
||||
providers = PROVIDERS["offline"]
|
||||
if not offline_only:
|
||||
providers += PROVIDERS["online"]
|
||||
|
||||
# Run through all available libc database providers to see if we have a match.
|
||||
for provider in PROVIDERS:
|
||||
data = provider(hex_encoded_id, hash_type)
|
||||
for provider in providers:
|
||||
data = provider(search_target, search_type)
|
||||
if data and data.startswith(b'\x7FELF'):
|
||||
break
|
||||
|
||||
if not data:
|
||||
log.warn_once("Could not find libc for %s %s anywhere", hash_type, hex_encoded_id)
|
||||
log.warn_once("Could not find libc for %s %s anywhere", search_type, search_target)
|
||||
|
||||
# Save whatever we got to the cache
|
||||
write(cache, data or b'')
|
||||
@@ -130,37 +296,7 @@ def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
|
||||
|
||||
return cache
|
||||
|
||||
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
from six.moves import urllib
|
||||
|
||||
# Check if we tried this buildid before.
|
||||
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
|
||||
if cache_valid:
|
||||
return cache
|
||||
|
||||
# Try to find separate debuginfo.
|
||||
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
|
||||
url = urllib.parse.urljoin(base_url, url)
|
||||
data = b""
|
||||
log.debug("Downloading data from debuginfod: %s", url)
|
||||
try:
|
||||
data = wget(url, timeout=20)
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, base_url, e)
|
||||
|
||||
# Save whatever we got to the cache
|
||||
write(cache, data or b'')
|
||||
|
||||
# Return ``None`` if we did not get a valid ELF file
|
||||
if not data or not data.startswith(b'\x7FELF'):
|
||||
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, base_url)
|
||||
return None
|
||||
|
||||
return cache
|
||||
|
||||
def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
|
||||
def _check_elf_cache(cache_type, search_target, search_type):
|
||||
"""
|
||||
Check if there already is an ELF file for this hash in the cache.
|
||||
|
||||
@@ -173,14 +309,14 @@ def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
|
||||
True
|
||||
"""
|
||||
# Ensure that the cache directory exists
|
||||
cache_dir = os.path.join(context.cache_dir, cache_type, hash_type)
|
||||
cache_dir = os.path.join(context.cache_dir, cache_type, search_type)
|
||||
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
# If we already downloaded the file, and it looks even passingly like
|
||||
# a valid ELF file, return it.
|
||||
cache = os.path.join(cache_dir, hex_encoded_id)
|
||||
cache = os.path.join(cache_dir, search_target)
|
||||
|
||||
if not os.path.exists(cache):
|
||||
return cache, False
|
||||
@@ -189,8 +325,11 @@ def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
|
||||
|
||||
data = read(cache)
|
||||
if not data.startswith(b'\x7FELF'):
|
||||
log.info_once("Skipping unavailable ELF %s", hex_encoded_id)
|
||||
return cache, False
|
||||
# Retry failed lookups after some time
|
||||
if time.time() > os.path.getmtime(cache) + NEGATIVE_CACHE_EXPIRY:
|
||||
return cache, False
|
||||
log.info_once("Skipping invalid cached ELF %s", search_target)
|
||||
return None, False
|
||||
|
||||
log.info_once("Using cached data from %r", cache)
|
||||
return cache, True
|
||||
@@ -210,19 +349,20 @@ def unstrip_libc(filename):
|
||||
:const:`True` if binary was unstripped, :const:`False` otherwise.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> filename = search_by_build_id('69389d485a9793dbe873f0ea2c93e02efaa9aa3d', unstrip=False)
|
||||
>>> libc = ELF(filename)
|
||||
>>> 'main_arena' in libc.symbols
|
||||
False
|
||||
>>> unstrip_libc(filename)
|
||||
>>> unstrip_libc(filename) # doctest: +SKIP
|
||||
True
|
||||
>>> libc = ELF(filename)
|
||||
>>> hex(libc.symbols.main_arena)
|
||||
>>> hex(libc.symbols.main_arena) # doctest: +SKIP
|
||||
'0x219c80'
|
||||
>>> unstrip_libc(pwnlib.data.elf.get('test-x86'))
|
||||
False
|
||||
>>> filename = search_by_build_id('d1704d25fbbb72fa95d517b883131828c0883fe9', unstrip=True)
|
||||
>>> 'main_arena' in ELF(filename).symbols
|
||||
>>> 'main_arena' in ELF(filename).symbols # doctest: +SKIP
|
||||
True
|
||||
"""
|
||||
if not which('eu-unstrip'):
|
||||
@@ -234,18 +374,51 @@ def unstrip_libc(filename):
|
||||
log.warn_once('Given libc does not have a buildid. Cannot look for debuginfo to unstrip.')
|
||||
return False
|
||||
|
||||
if libc.debuginfo:
|
||||
log.debug('Given libc already contains debug information. Skipping unstrip.')
|
||||
return True
|
||||
|
||||
log.debug('Trying debuginfod servers: %r', DEBUGINFOD_SERVERS)
|
||||
|
||||
for server_url in DEBUGINFOD_SERVERS:
|
||||
libc_dbg = _search_debuginfo_by_hash(server_url, enhex(libc.buildid))
|
||||
if libc_dbg:
|
||||
break
|
||||
else:
|
||||
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
|
||||
return False
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
from six.moves import urllib
|
||||
|
||||
hex_encoded_id = enhex(libc.buildid)
|
||||
|
||||
# Check if we tried this buildid before.
|
||||
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
|
||||
if not cache_valid:
|
||||
# Cached negative result so we don't look for missing debug info everytime.
|
||||
if cache is None:
|
||||
return False
|
||||
else:
|
||||
for server_url in DEBUGINFOD_SERVERS:
|
||||
# Try to find separate debuginfo.
|
||||
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
|
||||
url = urllib.parse.urljoin(server_url, url)
|
||||
data = b""
|
||||
log.debug("Downloading data from debuginfod: %s", url)
|
||||
try:
|
||||
data = wget(url, timeout=20)
|
||||
|
||||
# Try next server if we didn't get a valid ELF file
|
||||
if not data or not data.startswith(b'\x7FELF'):
|
||||
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, server_url)
|
||||
continue
|
||||
break
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, server_url, e)
|
||||
else:
|
||||
write(cache, data or b'')
|
||||
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
|
||||
return False
|
||||
|
||||
# Save whatever we got to the cache
|
||||
write(cache, data or b'')
|
||||
|
||||
# Add debug info to given libc binary inplace.
|
||||
p = process(['eu-unstrip', '-o', filename, filename, libc_dbg])
|
||||
p = process(['eu-unstrip', '-o', filename, filename, cache])
|
||||
output = p.recvall()
|
||||
p.close()
|
||||
|
||||
@@ -255,6 +428,214 @@ def unstrip_libc(filename):
|
||||
|
||||
return True
|
||||
|
||||
def _extract_tarfile(cache_dir, data_filename, tarball):
|
||||
from six import BytesIO
|
||||
import tarfile
|
||||
# Handle zstandard compression, since tarfile only supports gz, bz2, and xz.
|
||||
if data_filename.endswith('.zst') or data_filename.endswith('.zstd'):
|
||||
import zstandard
|
||||
dctx = zstandard.ZstdDecompressor()
|
||||
decompressed_tar = BytesIO()
|
||||
dctx.copy_stream(tarball, decompressed_tar)
|
||||
decompressed_tar.seek(0)
|
||||
tarball.close()
|
||||
tarball = decompressed_tar
|
||||
|
||||
if six.PY2 and data_filename.endswith('.xz'):
|
||||
# Python 2's tarfile doesn't support xz, so we need to decompress it first.
|
||||
# Shell out to xz, since the Python 2 pylzma module is broken.
|
||||
# (https://github.com/fancycode/pylzma/issues/67)
|
||||
if not which('xz'):
|
||||
log.error('Couldn\'t find "xz" in PATH. Please install xz first.')
|
||||
import subprocess
|
||||
try:
|
||||
uncompressed_tarball = subprocess.check_output(['xz', '--decompress', '--stdout', tarball.name])
|
||||
tarball = BytesIO(uncompressed_tarball)
|
||||
except subprocess.CalledProcessError:
|
||||
log.error('Failed to decompress xz archive.')
|
||||
|
||||
with tarfile.open(fileobj=tarball) as tar_file:
|
||||
# Find the library folder in the archive (e.g. /lib/x86_64-linux-gnu/)
|
||||
lib_dir = None
|
||||
libc_name = None
|
||||
for member in tar_file.getmembers():
|
||||
if not member.isfile():
|
||||
continue
|
||||
libc_name = os.path.basename(member.name)
|
||||
if libc_name == 'libc.so.6' or (libc_name.startswith('libc') and libc_name.endswith('.so')):
|
||||
lib_dir = os.path.dirname(member.name)
|
||||
break
|
||||
else:
|
||||
log.error('Couldn\'t find library folder containing the libc in the archive.')
|
||||
|
||||
# Extract everything in the library folder
|
||||
for member in tar_file.getmembers():
|
||||
if os.path.dirname(member.name) != lib_dir:
|
||||
continue
|
||||
if not member.isfile() and not member.issym():
|
||||
continue
|
||||
# Extract while keeping file permissions
|
||||
tar_file.extract(member, cache_dir)
|
||||
|
||||
# Move the files up to the cache root
|
||||
target_dir = os.path.join(cache_dir, lib_dir)
|
||||
for file in os.listdir(target_dir):
|
||||
os.rename(os.path.join(target_dir, file), os.path.join(cache_dir, file))
|
||||
os.removedirs(target_dir)
|
||||
|
||||
return os.path.join(cache_dir, libc_name)
|
||||
|
||||
def _extract_debfile(cache_dir, package_filename, package):
|
||||
# Extract data.tar in the .deb archive.
|
||||
if sys.version_info < (3, 6):
|
||||
if not which('ar'):
|
||||
log.error('Missing command line tool "ar" to extract .deb archive. Please install "ar" first.')
|
||||
|
||||
import atexit
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
# Use mkdtemp instead of TemporaryDirectory because the latter is not available in Python 2.
|
||||
tempdir = tempfile.mkdtemp(prefix=".pwntools-tmp")
|
||||
atexit.register(shutil.rmtree, tempdir)
|
||||
with tempfile.NamedTemporaryFile(mode='wb', dir=tempdir) as debfile:
|
||||
debfile.write(package)
|
||||
debfile.flush()
|
||||
try:
|
||||
files_in_deb = subprocess.check_output(['ar', 't', debfile.name]).split(b'\n')
|
||||
except subprocess.CalledProcessError:
|
||||
log.error('Failed to list files in .deb archive.')
|
||||
[data_filename] = filter(lambda f: f.startswith(b'data.tar'), files_in_deb)
|
||||
|
||||
try:
|
||||
subprocess.check_call(['ar', 'x', debfile.name, data_filename], cwd=tempdir)
|
||||
except subprocess.CalledProcessError:
|
||||
log.error('Failed to extract data.tar from .deb archive.')
|
||||
|
||||
with open(os.path.join(tempdir, data_filename), 'rb') as tarball:
|
||||
return _extract_tarfile(cache_dir, data_filename, tarball)
|
||||
else:
|
||||
import unix_ar
|
||||
from six import BytesIO
|
||||
ar_file = unix_ar.open(BytesIO(package))
|
||||
try:
|
||||
data_filename = next(filter(lambda f: f.name.startswith(b'data.tar'), ar_file.infolist())).name.decode()
|
||||
tarball = ar_file.open(data_filename)
|
||||
return _extract_tarfile(cache_dir, data_filename, tarball)
|
||||
finally:
|
||||
ar_file.close()
|
||||
|
||||
def _extract_pkgfile(cache_dir, package_filename, package):
|
||||
from six import BytesIO
|
||||
return _extract_tarfile(cache_dir, package_filename, BytesIO(package))
|
||||
|
||||
def _find_libc_package_lib_url(libc):
|
||||
# Check https://libc.rip for the libc package
|
||||
libc_match = query_libc_rip({'buildid': enhex(libc.buildid)})
|
||||
if libc_match is not None:
|
||||
for match in libc_match:
|
||||
# Allow to override url with a caching proxy in CI
|
||||
ubuntu_archive_url = os.environ.get('PWN_UBUNTU_ARCHIVE_URL', 'http://archive.ubuntu.com').rstrip('/')
|
||||
yield match['libs_url'].replace('http://archive.ubuntu.com', ubuntu_archive_url)
|
||||
|
||||
# Check launchpad.net if it's an Ubuntu libc
|
||||
# GNU C Library (Ubuntu GLIBC 2.36-0ubuntu4)
|
||||
import re
|
||||
version = re.search(br'GNU C Library \(Ubuntu E?GLIBC ([^\)]+)\)', libc.data)
|
||||
if version is not None:
|
||||
libc_version = version.group(1).decode()
|
||||
yield 'https://launchpad.net/ubuntu/+archive/primary/+files/libc6_{}_{}.deb'.format(libc_version, libc.arch)
|
||||
|
||||
def download_libraries(libc_path, unstrip=True):
|
||||
"""download_libraries(str, bool) -> str
|
||||
Download the matching libraries for the given libc binary and cache
|
||||
them in a local directory. The libraries are looked up using `libc.rip <https://libc.rip>`_
|
||||
and fetched from the official package repositories if available.
|
||||
|
||||
This commonly includes the ``ld-linux-x86-64.so.2`` and ``libpthread.so.0`` binaries
|
||||
which can be used to execute the program locally when the given libc is
|
||||
incompatible with the local dynamic loader.
|
||||
|
||||
Note: Only .deb and .pkg.tar.* packages are currently supported (Debian/Ubuntu, Arch).
|
||||
|
||||
Arguments:
|
||||
libc_path(str):
|
||||
The path the libc binary.
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
|
||||
Returns:
|
||||
The path to the cached directory containing the downloaded libraries.
|
||||
|
||||
Example:
|
||||
|
||||
>>> libc_path = ELF(which('ls'), checksec=False).libc.path
|
||||
>>> lib_path = download_libraries(libc_path)
|
||||
>>> lib_path is not None
|
||||
True
|
||||
>>> os.path.exists(os.path.join(lib_path, 'libc.so.6'))
|
||||
True
|
||||
>>> os.path.exists(os.path.join(lib_path, 'ld-linux-x86-64.so.2'))
|
||||
True
|
||||
"""
|
||||
|
||||
libc = ELF(libc_path, checksec=False)
|
||||
if not libc.buildid:
|
||||
log.warn_once('Given libc does not have a buildid.')
|
||||
return None
|
||||
|
||||
# Handle caching and don't redownload if it already exists.
|
||||
cache_dir = os.path.join(context.cache_dir, 'libcdb_libs')
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
cache_dir = os.path.join(cache_dir, enhex(libc.buildid))
|
||||
if os.path.exists(cache_dir):
|
||||
return cache_dir
|
||||
|
||||
for package_url in _find_libc_package_lib_url(libc):
|
||||
extension_handlers = {
|
||||
'.deb': _extract_debfile,
|
||||
'.pkg.tar.xz': _extract_pkgfile,
|
||||
'.pkg.tar.zst': _extract_pkgfile,
|
||||
}
|
||||
|
||||
package_filename = os.path.basename(package_url)
|
||||
for extension, handler in extension_handlers.items():
|
||||
if package_filename.endswith(extension):
|
||||
break
|
||||
else:
|
||||
log.failure('Cannot handle %s (%s)', package_filename, package_url)
|
||||
continue
|
||||
|
||||
# Download the package
|
||||
package = wget(package_url, timeout=20)
|
||||
if not package:
|
||||
continue
|
||||
|
||||
# Create target cache directory to extract files into
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
try:
|
||||
# Extract the archive
|
||||
libc_path = handler(cache_dir, package_filename, package)
|
||||
except Exception as e:
|
||||
os.removedirs(cache_dir)
|
||||
log.failure('Failed to extract %s: %s', package_filename, e)
|
||||
continue
|
||||
# Unstrip the libc binary
|
||||
try:
|
||||
if unstrip:
|
||||
unstrip_libc(libc_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return cache_dir
|
||||
|
||||
log.warn_once('Failed to find matching libraries for provided libc.')
|
||||
return None
|
||||
|
||||
def _handle_multiple_matching_libcs(matching_libcs):
|
||||
from pwnlib.term import text
|
||||
from pwnlib.ui import options
|
||||
@@ -272,7 +653,7 @@ def _handle_multiple_matching_libcs(matching_libcs):
|
||||
selected_index = options("Select the libc version to use:", [libc['id'] for libc in matching_libcs])
|
||||
return matching_libcs[selected_index]
|
||||
|
||||
def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as_list=False):
|
||||
def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, offline_only=False, search_type='build_id', return_as_list=False, return_raw=False):
|
||||
"""
|
||||
Lookup possible matching libc versions based on leaked function addresses.
|
||||
|
||||
@@ -291,9 +672,16 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as
|
||||
The libc to select if there are multiple matches (starting at 1).
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
When pass `offline_only=True`, restricts search mode to offline sources only,
|
||||
disable online lookup. Defaults to `False`, and enable both offline and online providers.
|
||||
search_type(str):
|
||||
An option to select searched hash.
|
||||
return_as_list(bool):
|
||||
Return a list of build ids of all matching libc versions
|
||||
instead of a path to a downloaded file.
|
||||
return_raw(bool):
|
||||
Return raw list of matched libc.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
@@ -301,6 +689,7 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as
|
||||
is returned instead.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> filename = search_by_symbol_offsets({'puts': 0x420, 'printf': 0xc90}, select_index=1)
|
||||
>>> libc = ELF(filename)
|
||||
>>> libc.sym.system == 0x52290
|
||||
@@ -311,43 +700,91 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as
|
||||
>>> for buildid in matched_libcs: # doctest +SKIP
|
||||
... libc = ELF(search_by_build_id(buildid)) # doctest +SKIP
|
||||
"""
|
||||
import requests
|
||||
assert search_type in TYPES, search_type
|
||||
|
||||
for symbol, address in symbols.items():
|
||||
if isinstance(address, int):
|
||||
symbols[symbol] = hex(address)
|
||||
try:
|
||||
params = {'symbols': symbols}
|
||||
url = "https://libc.rip/api/find"
|
||||
log.debug('Request: %s', params)
|
||||
result = requests.post(url, json=params, timeout=20)
|
||||
result.raise_for_status()
|
||||
|
||||
matching_libcs = result.json()
|
||||
log.debug('Result: %s', matching_libcs)
|
||||
if len(matching_libcs) == 0:
|
||||
log.warn_once("No matching libc for symbols %r on libc.rip", symbols)
|
||||
return None
|
||||
params = {'symbols': symbols}
|
||||
log.debug('Request: %s', params)
|
||||
|
||||
if return_as_list:
|
||||
return [libc['buildid'] for libc in matching_libcs]
|
||||
offline_matching = query_local_database(params)
|
||||
online_matching = query_libc_rip(params) if not offline_only else None
|
||||
|
||||
if len(matching_libcs) == 1:
|
||||
return search_by_build_id(matching_libcs[0]['buildid'], unstrip=unstrip)
|
||||
if offline_matching is None:
|
||||
offline_matching = []
|
||||
if online_matching is None:
|
||||
online_matching = []
|
||||
|
||||
if select_index is not None:
|
||||
if select_index > 0 and select_index <= len(matching_libcs):
|
||||
return search_by_build_id(matching_libcs[select_index - 1]['buildid'], unstrip=unstrip)
|
||||
else:
|
||||
log.error('Invalid selected libc index. %d is not in the range of 1-%d.', select_index, len(matching_libcs))
|
||||
return None
|
||||
# Aggregate and deduplicate matches from both sources
|
||||
matching_libcs = {}
|
||||
for libc in offline_matching + online_matching:
|
||||
if libc['id'] not in matching_libcs:
|
||||
matching_libcs[libc['id']] = libc
|
||||
|
||||
selected_libc = _handle_multiple_matching_libcs(matching_libcs)
|
||||
return search_by_build_id(selected_libc['buildid'], unstrip=unstrip)
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to lookup libc for symbols %r from libc.rip: %s", symbols, e)
|
||||
log.debug('Offline search result: %s', offline_matching)
|
||||
if not offline_only:
|
||||
log.debug('Online search result: %s', online_matching)
|
||||
|
||||
# Check if no matches are found
|
||||
if len(matching_libcs) == 0:
|
||||
log.warn_once("No matching libc for symbols %r", symbols)
|
||||
return None
|
||||
|
||||
def search_by_build_id(hex_encoded_id, unstrip=True):
|
||||
matching_list = list(matching_libcs.values())
|
||||
|
||||
if return_as_list:
|
||||
return [libc['buildid'] for libc in matching_list]
|
||||
|
||||
if return_raw:
|
||||
return matching_list
|
||||
|
||||
mapped_type = MAP_TYPES.get(search_type, search_type)
|
||||
|
||||
# If there's only one match, return it directly
|
||||
if len(matching_list) == 1:
|
||||
return search_by_hash(matching_list[0][mapped_type], search_type=search_type, unstrip=unstrip, offline_only=offline_only)
|
||||
|
||||
# If a specific index is provided, validate it and return the selected libc
|
||||
if select_index is not None:
|
||||
if select_index > 0 and select_index <= len(matching_list):
|
||||
return search_by_hash(matching_list[select_index - 1][mapped_type], search_type=search_type, unstrip=unstrip, offline_only=offline_only)
|
||||
else:
|
||||
log.error('Invalid selected libc index. %d is not in the range of 1-%d.', select_index, len(matching_list))
|
||||
return None
|
||||
|
||||
# Handle multiple matches interactively if no index is specified
|
||||
selected_libc = _handle_multiple_matching_libcs(matching_list)
|
||||
return search_by_hash(selected_libc[mapped_type], search_type=search_type, unstrip=unstrip, offline_only=offline_only)
|
||||
|
||||
def search_by_libs_id(libs_id, unstrip=True, offline_only=False):
|
||||
"""
|
||||
Given a Libs ID, attempt to download a matching libc from libcdb.
|
||||
|
||||
Arguments:
|
||||
libs_id(str):
|
||||
Libs ID (e.g. ``'libc6_...'``) of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
When pass ``offline_only=True``, restricts search mode to offline sources only,
|
||||
disable online lookup. Defaults to :const:`False`, and enable both offline and online providers.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> None == search_by_libs_id('XX')
|
||||
True
|
||||
>>> filename = search_by_libs_id('libc6_2.31-3_amd64')
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(libs_id, 'libs_id', unstrip, offline_only)
|
||||
|
||||
def search_by_build_id(hex_encoded_id, unstrip=True, offline_only=False):
|
||||
"""
|
||||
Given a hex-encoded Build ID, attempt to download a matching libc from libcdb.
|
||||
|
||||
@@ -356,11 +793,15 @@ def search_by_build_id(hex_encoded_id, unstrip=True):
|
||||
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
When pass `offline_only=True`, restricts search mode to offline sources only,
|
||||
disable online lookup. Defaults to `False`, and enable both offline and online providers.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> filename = search_by_build_id('fe136e485814fee2268cf19e5c124ed0f73f4400')
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xda260'
|
||||
@@ -370,9 +811,9 @@ def search_by_build_id(hex_encoded_id, unstrip=True):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'build_id', unstrip)
|
||||
return search_by_hash(hex_encoded_id, 'build_id', unstrip, offline_only)
|
||||
|
||||
def search_by_md5(hex_encoded_id, unstrip=True):
|
||||
def search_by_md5(hex_encoded_id, unstrip=True, offline_only=False):
|
||||
"""
|
||||
Given a hex-encoded md5sum, attempt to download a matching libc from libcdb.
|
||||
|
||||
@@ -381,11 +822,15 @@ def search_by_md5(hex_encoded_id, unstrip=True):
|
||||
Hex-encoded md5sum (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
When pass `offline_only=True`, restricts search mode to offline sources only,
|
||||
disable online lookup. Defaults to `False`, and enable both offline and online providers.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> filename = search_by_md5('7a71dafb87606f360043dcd638e411bd')
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xda260'
|
||||
@@ -395,9 +840,9 @@ def search_by_md5(hex_encoded_id, unstrip=True):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'md5', unstrip)
|
||||
return search_by_hash(hex_encoded_id, 'md5', unstrip, offline_only)
|
||||
|
||||
def search_by_sha1(hex_encoded_id, unstrip=True):
|
||||
def search_by_sha1(hex_encoded_id, unstrip=True, offline_only=False):
|
||||
"""
|
||||
Given a hex-encoded sha1, attempt to download a matching libc from libcdb.
|
||||
|
||||
@@ -406,11 +851,15 @@ def search_by_sha1(hex_encoded_id, unstrip=True):
|
||||
Hex-encoded sha1sum (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
When pass `offline_only=True`, restricts search mode to offline sources only,
|
||||
disable online lookup. Defaults to `False`, and enable both offline and online providers.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> filename = search_by_sha1('34471e355a5e71400b9d65e78d2cd6ce7fc49de5')
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xda260'
|
||||
@@ -420,10 +869,9 @@ def search_by_sha1(hex_encoded_id, unstrip=True):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'sha1', unstrip)
|
||||
return search_by_hash(hex_encoded_id, 'sha1', unstrip, offline_only)
|
||||
|
||||
|
||||
def search_by_sha256(hex_encoded_id, unstrip=True):
|
||||
def search_by_sha256(hex_encoded_id, unstrip=True, offline_only=False):
|
||||
"""
|
||||
Given a hex-encoded sha256, attempt to download a matching libc from libcdb.
|
||||
|
||||
@@ -432,11 +880,15 @@ def search_by_sha256(hex_encoded_id, unstrip=True):
|
||||
Hex-encoded sha256sum (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
offline_only(bool):
|
||||
When pass `offline_only=True`, restricts search mode to offline sources only,
|
||||
disable online lookup. Defaults to `False`, and enable both offline and online providers.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> filename = search_by_sha256('5e877a8272da934812d2d1f9ee94f73c77c790cbc5d8251f5322389fc9667f21')
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xda260'
|
||||
@@ -446,9 +898,54 @@ def search_by_sha256(hex_encoded_id, unstrip=True):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'sha256', unstrip)
|
||||
return search_by_hash(hex_encoded_id, 'sha256', unstrip, offline_only)
|
||||
|
||||
def _parse_libc_symbol(path):
|
||||
"""
|
||||
Parse symbols file to `dict`, the format is same as https://github.com/niklasb/libc-database/
|
||||
"""
|
||||
|
||||
syms = {}
|
||||
|
||||
with open(path, "r") as fd:
|
||||
for x in fd:
|
||||
name, addr = x.split(" ")
|
||||
syms[name] = int(addr, 16)
|
||||
|
||||
return syms
|
||||
|
||||
def _pack_libs_info(path, libs_id, libs_url, syms):
|
||||
"""
|
||||
The JSON format is the same as libc.rip, and the "download_url" field is by default an empty string,
|
||||
as it's not required in offline mode.
|
||||
"""
|
||||
|
||||
info = {}
|
||||
|
||||
info["id"] = libs_id
|
||||
info["libs_url"] = libs_url
|
||||
info["download_url"] = ""
|
||||
|
||||
for search_type, hash_func in TYPES.items():
|
||||
# pass libs_id
|
||||
if search_type == 'libs_id':
|
||||
continue
|
||||
|
||||
# replace search_type
|
||||
if search_type in MAP_TYPES.keys():
|
||||
search_type = MAP_TYPES[search_type]
|
||||
|
||||
info[search_type] = hash_func(path)
|
||||
|
||||
default_symbol_list = [
|
||||
"__libc_start_main_ret", "dup2", "printf", "puts", "read", "system", "str_bin_sh"
|
||||
]
|
||||
|
||||
info["symbols"] = {}
|
||||
for name in default_symbol_list:
|
||||
info["symbols"][name] = hex(syms[name])
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def get_build_id_offsets():
|
||||
@@ -503,4 +1000,4 @@ def get_build_id_offsets():
|
||||
}.get(context.arch, [])
|
||||
|
||||
|
||||
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'unstrip_libc', 'search_by_symbol_offsets']
|
||||
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'search_by_libs_id', 'unstrip_libc', 'search_by_symbol_offsets', 'download_libraries']
|
||||
Loaded 100 of 706 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user