From 92cd96999fe28e6955ff9dc7856117d775bf56f5 Mon Sep 17 00:00:00 2001 From: goreil <90871590+goreil@users.noreply.github.com> Date: Fri, 15 Sep 2023 01:10:14 +0200 Subject: [PATCH 01/21] Allow empty argv in ssh.process() (#2217) (#2234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix bug at ssh.py:process() - empty argv[0] Error Before this, process.py relied on `os.execve` which disallows an empty argv or argv[0] == "". This commit replaces `os.execve` with `ctypes.CDLL(None).execve` to call the C-Library function directly which allows an empty argv. * Add #2225 to stable changelog * Better ctypes syntax * Add error message if cytpes.execve fails. * Updata CHANGELOG.md * ssh.py: python2 compatibility for os.environb * Add check that "=" not in misc.normalize_argv_env This check checks prevents the use of "=" in the key of an environment variable, which is generally impossible. * ssh.process: Seperate cases for empty argv[0] This commit seperates the cases for an empty argv[0] from normal cases. * ssh.py delete leftover comment --------- Co-authored-by: Youheng Lü <90871590+Youheng-Lue@users.noreply.github.com> Co-authored-by: Arusekk Co-authored-by: peace-maker --- CHANGELOG.md | 4 +++- pwnlib/tubes/ssh.py | 52 +++++++++++++++++++++++++++++++++++++++++---- pwnlib/util/misc.py | 4 ++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa8ad2b2d..94f235077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ The table below shows which release corresponds to each branch, and what date th - [#2219][2219] Fix passing arguments on the stack in shellcraft syscall template - [#2212][2212] Add `--libc libc.so` argument to `pwn template` command - [#2257][2257] Allow creation of custom templates for `pwn template` command +- [#2225][2225] Allow empty argv in ssh.process() [2202]: https://github.com/Gallopsled/pwntools/pull/2202 [2117]: https://github.com/Gallopsled/pwntools/pull/2117 @@ -80,6 +81,7 @@ The table below shows which release corresponds to each branch, and what date th [2219]: https://github.com/Gallopsled/pwntools/pull/2219 [2212]: https://github.com/Gallopsled/pwntools/pull/2212 [2257]: https://github.com/Gallopsled/pwntools/pull/2257 +[2225]: https://github.com/Gallopsled/pwntools/pull/2225 ## 4.11.0 (`beta`) @@ -95,7 +97,7 @@ The table below shows which release corresponds to each branch, and what date th [2186]: https://github.com/Gallopsled/pwntools/pull/2186 [2129]: https://github.com/Gallopsled/pwntools/pull/2129 -## 4.10.1 (`stable`) +## 4.10.1 (`stable`) - [#2214][2214] Fix bug at ssh.py:`download` and `download_file` with relative paths - [#2241][2241] Fix ssh.process not setting ssh_process.cwd attribute diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py index 33978cec3..2216da0af 100644 --- a/pwnlib/tubes/ssh.py +++ b/pwnlib/tubes/ssh.py @@ -882,6 +882,23 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time >>> io = s.process(['cat'], timeout=5) >>> io.recvline() b'' + + >>> # Testing that empty argv works + >>> io = s.process([], executable='sh') + >>> io.sendline(b'echo $0') + >>> io.recvline() + b'$ \n' + >>> # Make sure that we have a shell + >>> io.sendline(b'echo hello') + >>> io.recvline() + b'$ hello\n' + + >>> # Testing that empty argv[0] works + >>> io = s.process([''], executable='sh') + >>> io.sendline(b'echo $0') + >>> io.recvline() + b'$ \n' + """ if not argv and not executable: self.error("Must specify argv or executable") @@ -945,7 +962,7 @@ def func(): pass os.environ.clear() environ.update(env) else: - env = os.environ + env = environ def is_exe(path): return os.path.isfile(path) and os.access(path, os.X_OK) @@ -1034,8 +1051,35 @@ def is_exe(path): %(func_src)s %(func_name)s(*%(func_args)r) -os.execve(exe, argv, env) -""" % locals() # """ +""" % locals() + + if len(argv) > 0 and len(argv[0]) > 0: + script += r"os.execve(exe, argv, env) " + + # os.execve does not allow us to pass empty argv[0] + # Therefore we use ctypes to call execve directly + else: + script += r""" +# Transform envp from dict to list +env_list = [key + b"=" + value for key, value in env.items()] + +# ctypes helper to convert a python list to a NULL-terminated C array +def to_carray(py_list): + py_list += [None] # NULL-terminated + return (ctypes.c_char_p * len(py_list))(*py_list) + +c_argv = to_carray(argv) +c_env = to_carray(env_list) + +# Call execve +libc = ctypes.CDLL('libc.so.6') +libc.execve(exe, c_argv, c_env) + +# We should never get here, since we sanitized argv and env, +# but just in case, indicate that something went wrong. +libc.perror(b"execve") +raise OSError("execve failed") +""" % locals() script = script.strip() @@ -1054,7 +1098,7 @@ def is_exe(path): execve_repr = "execve(%r, %s, %s)" % (executable, argv, 'os.environ' - if (env in (None, os.environ)) + if (env in (None, getattr(os, "environb", os.environ))) else env) # Avoid spamming the screen if self.isEnabledFor(logging.DEBUG) and len(execve_repr) > 512: diff --git a/pwnlib/util/misc.py b/pwnlib/util/misc.py index 7fbf479ca..0b7fbf456 100644 --- a/pwnlib/util/misc.py +++ b/pwnlib/util/misc.py @@ -229,6 +229,10 @@ def normalize_argv_env(argv, env, log, level=2): for k,v in env_items: if not isinstance(k, (bytes, six.text_type)): log.error('Environment keys must be strings: %r' % k) + # Check if = is in the key, Required check since we sometimes call ctypes.execve directly + # https://github.com/python/cpython/blob/025995feadaeebeef5d808f2564f0fd65b704ea5/Modules/posixmodule.c#L6476 + if b'=' in packing._encode(k): + log.error('Environment keys may not contain "=": %r' % (k)) if not isinstance(v, (bytes, six.text_type)): log.error('Environment values must be strings: %r=%r' % (k,v)) k = packing._need_bytes(k, level, 0x80) # ASCII text is okay From 6aaaf84586b4c807a516bb50148eccdc32af68cf Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Fri, 15 Sep 2023 23:16:19 +0200 Subject: [PATCH 02/21] Release 4.11.0 --- CHANGELOG.md | 38 ++++++++++++++++++++++++++------------ pwnlib/version.py | 2 +- setup.py | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 871ef5fab..17c1a2071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,10 @@ The table below shows which release corresponds to each branch, and what date th | Version | Branch | Release Date | | ---------------- | -------- | ---------------------- | -| [4.12.0](#4120) | `dev` | -| [4.11.0](#4110) | `beta` | -| [4.10.0](#4100) | `stable` | May 21, 2023 +| [4.13.0](#4130-dev) | `dev` | +| [4.12.0](#4120-beta) | `beta` | +| [4.11.0](#4110-stable) | `stable` | Sep 15, 2023 +| [4.10.0](#4100) | | May 21, 2023 | [4.9.0](#490) | | Dec 29, 2022 | [4.8.0](#480) | | Apr 21, 2022 | [4.7.1](#471) | | Apr 20, 2022 @@ -66,30 +67,43 @@ 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.12.0 (`dev`) +## 4.13.0 (`dev`) -## 4.11.0 (`beta`) +## 4.12.0 (`beta`) +- [#2202][2202] Fix `remote` and `listen` in sagemath +- [#2117][2117] Add -p (--prefix) and -s (--separator) arguments to `hex` command +- [#2221][2221] Add shellcraft.sleep template wrapping SYS_nanosleep +- [#2219][2219] Fix passing arguments on the stack in shellcraft syscall template +- [#2212][2212] Add `--libc libc.so` argument to `pwn template` command +- [#2257][2257] Allow creation of custom templates for `pwn template` command +- [#2225][2225] Allow empty argv in ssh.process() + +[2202]: https://github.com/Gallopsled/pwntools/pull/2202 +[2117]: https://github.com/Gallopsled/pwntools/pull/2117 +[2221]: https://github.com/Gallopsled/pwntools/pull/2221 +[2219]: https://github.com/Gallopsled/pwntools/pull/2219 +[2212]: https://github.com/Gallopsled/pwntools/pull/2212 +[2257]: https://github.com/Gallopsled/pwntools/pull/2257 +[2225]: https://github.com/Gallopsled/pwntools/pull/2225 + +## 4.11.0 (`stable`) - [#2185][2185] make fmtstr module able to create payload without $ notation - [#2103][2103] Add search for libc binary by leaked function addresses `libcdb.search_by_symbol_offsets()` - [#2177][2177] Support for RISC-V 64-bit architecture - [#2186][2186] Enhance `ELF.nx` and `ELF.execstack` - [#2129][2129] Handle `context.newline` correctly when typing in `tube.interactive()` +- [#2214][2214] Fix bug at ssh.py:`download` and `download_file` with relative paths +- [#2241][2241] Fix ssh.process not setting ssh_process.cwd attribute +- [#2261][2261] Fix corefile module after pyelftools update [2185]: https://github.com/Gallopsled/pwntools/pull/2185 [2103]: https://github.com/Gallopsled/pwntools/pull/2103 [2177]: https://github.com/Gallopsled/pwntools/pull/2177 [2186]: https://github.com/Gallopsled/pwntools/pull/2186 [2129]: https://github.com/Gallopsled/pwntools/pull/2129 - -## 4.10.1 (`stable`) - -- [#2214][2214] Fix bug at ssh.py:`download` and `download_file` with relative paths -- [#2241][2241] Fix ssh.process not setting ssh_process.cwd attribute -- [#2261][2261] Fix corefile module after pyelftools update - [2214]: https://github.com/Gallopsled/pwntools/pull/2214 [2241]: https://github.com/Gallopsled/pwntools/pull/2241 [2261]: https://github.com/Gallopsled/pwntools/pull/2261 diff --git a/pwnlib/version.py b/pwnlib/version.py index 249d82751..6c387e498 100644 --- a/pwnlib/version.py +++ b/pwnlib/version.py @@ -1 +1 @@ -__version__ = '4.11.0beta0' +__version__ = '4.11.0' diff --git a/setup.py b/setup.py index 416135734..e6bb61286 100755 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ sys.exit(-1) setup( - version = '4.11.0beta0', + version = '4.11.0', data_files = [('pwntools-doc', glob.glob('*.md') + glob.glob('*.txt')), ], From fd50d4c995ddd2c88691998f560b398f9579d37f Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Sat, 16 Sep 2023 00:44:57 +0200 Subject: [PATCH 03/21] Begin working on 4.13.0 --- pwnlib/version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnlib/version.py b/pwnlib/version.py index 07c49c1c0..94955abb8 100644 --- a/pwnlib/version.py +++ b/pwnlib/version.py @@ -1 +1 @@ -__version__ = '4.12.0beta0' +__version__ = '4.13.0dev' diff --git a/setup.py b/setup.py index 2f71aee4f..ee37b6735 100755 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ sys.exit(-1) setup( - version = '4.12.0beta0', + version = '4.13.0dev', data_files = [('pwntools-doc', glob.glob('*.md') + glob.glob('*.txt')), ], From 57d810acb11afa5f7f264642b50c496084c810ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Sep 2023 18:48:05 +0200 Subject: [PATCH 04/21] Bump docker/build-push-action from 4 to 5 (#2273) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7474cc4e6..b57858094 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -22,7 +22,7 @@ jobs: password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Build and push base image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 if: github.event_name == 'workflow_dispatch' with: context: "{{defaultContext}}:extra/docker/base" @@ -30,7 +30,7 @@ jobs: tags: pwntools/pwntools:base - name: Build and push stable image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/stable') with: context: "{{defaultContext}}:extra/docker/stable" @@ -38,7 +38,7 @@ jobs: tags: pwntools/pwntools:stable - name: Build and push beta image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/beta') with: context: "{{defaultContext}}:extra/docker/beta" @@ -46,7 +46,7 @@ jobs: tags: pwntools/pwntools:beta - name: Build and push dev image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/dev') with: context: "{{defaultContext}}:extra/docker/dev" @@ -56,7 +56,7 @@ jobs: pwntools/pwntools:latest - name: Build and push ci image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/dev') with: context: "{{defaultContext}}:travis/docker" From dd6b75445391a9c0665934e8966ef5d0b3a6842c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Sep 2023 18:48:28 +0200 Subject: [PATCH 05/21] Bump docker/setup-buildx-action from 2 to 3 (#2275) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b57858094..c2333eafc 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,7 +13,7 @@ jobs: steps: # Required for subdirectories in Git context - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v2 From 81d17c701c9eb06ca88eb063991689e821a8236a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Sep 2023 18:48:47 +0200 Subject: [PATCH 06/21] Bump docker/login-action from 2 to 3 (#2274) Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c2333eafc..b9fe7774f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -16,7 +16,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} From 32840ad8068bf584cd715e049f0a05e672fb2502 Mon Sep 17 00:00:00 2001 From: Gordiig Date: Sun, 1 Oct 2023 14:39:11 +0300 Subject: [PATCH 07/21] FIX: Getting right amount of data for search fix (#2281) * Getting right amount of data for search fix * Add line to CHANGELOG.md --- CHANGELOG.md | 3 ++- pwnlib/elf/elf.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c1a2071..eac87fc2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,8 +68,9 @@ The table below shows which release corresponds to each branch, and what date th | [2.2.0](#220) | | Jan 5, 2015 ## 4.13.0 (`dev`) +- [#2281][2281] FIX: Getting right amount of data for search fix - +[2281]: https://github.com/Gallopsled/pwntools/pull/2281 ## 4.12.0 (`beta`) - [#2202][2202] Fix `remote` and `listen` in sagemath diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py index 96244af8f..4a2437206 100644 --- a/pwnlib/elf/elf.py +++ b/pwnlib/elf/elf.py @@ -1195,9 +1195,10 @@ def search(self, needle, writable = False, executable = False): for seg in segments: addr = seg.header.p_vaddr memsz = seg.header.p_memsz - zeroed = memsz - seg.header.p_filesz + filesz = seg.header.p_filesz + zeroed = memsz - filesz offset = seg.header.p_offset - data = self.mmap[offset:offset+memsz] + data = self.mmap[offset:offset+filesz] data += b'\x00' * zeroed offset = 0 while True: From 045b8c2186d042f7c682afe55f003d2cb36916db Mon Sep 17 00:00:00 2001 From: Gordiig Date: Sun, 1 Oct 2023 14:39:11 +0300 Subject: [PATCH 08/21] FIX: Getting right amount of data for search fix (#2281) --- CHANGELOG.md | 11 +++++++++-- pwnlib/elf/elf.py | 5 +++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c1a2071..d21ad85ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ The table below shows which release corresponds to each branch, and what date th | ---------------- | -------- | ---------------------- | | [4.13.0](#4130-dev) | `dev` | | [4.12.0](#4120-beta) | `beta` | -| [4.11.0](#4110-stable) | `stable` | Sep 15, 2023 +| [4.11.1](#4111-stable) | `stable` | +| [4.11.0](#4110) | | Sep 15, 2023 | [4.10.0](#4100) | | May 21, 2023 | [4.9.0](#490) | | Dec 29, 2022 | [4.8.0](#480) | | Apr 21, 2022 @@ -88,7 +89,13 @@ The table below shows which release corresponds to each branch, and what date th [2257]: https://github.com/Gallopsled/pwntools/pull/2257 [2225]: https://github.com/Gallopsled/pwntools/pull/2225 -## 4.11.0 (`stable`) +## 4.11.1 (`stable`) + +- [#2281][2281] FIX: Getting right amount of data for search fix + +[2281]: https://github.com/Gallopsled/pwntools/pull/2281 + +## 4.11.0 - [#2185][2185] make fmtstr module able to create payload without $ notation - [#2103][2103] Add search for libc binary by leaked function addresses `libcdb.search_by_symbol_offsets()` diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py index 8bbf0b859..0fae91db1 100644 --- a/pwnlib/elf/elf.py +++ b/pwnlib/elf/elf.py @@ -1195,9 +1195,10 @@ def search(self, needle, writable = False, executable = False): for seg in segments: addr = seg.header.p_vaddr memsz = seg.header.p_memsz - zeroed = memsz - seg.header.p_filesz + filesz = seg.header.p_filesz + zeroed = memsz - filesz offset = seg.header.p_offset - data = self.mmap[offset:offset+memsz] + data = self.mmap[offset:offset+filesz] data += b'\x00' * zeroed offset = 0 while True: From 3ff37a8b7ef7e32ef219bd86790ed0130ba0c6a7 Mon Sep 17 00:00:00 2001 From: Tanix <56950803+TanixLu@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:23:31 +0800 Subject: [PATCH 09/21] Fix _countdown_handler not invoking timeout_change; Fix value is value < 0 (#2287) --- pwnlib/timeout.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pwnlib/timeout.py b/pwnlib/timeout.py index a1a4859f8..8e21a2d09 100644 --- a/pwnlib/timeout.py +++ b/pwnlib/timeout.py @@ -30,9 +30,11 @@ def __enter__(self): self.obj._stop = min(self.obj._stop, self.old_stop) self.obj._timeout = self.timeout + self.obj.timeout_change() def __exit__(self, *a): self.obj._timeout = self.old_timeout self.obj._stop = self.old_stop + self.obj.timeout_change() class _local_handler(object): def __init__(self, obj, timeout): @@ -157,7 +159,7 @@ def _get_timeout_seconds(self, value): else: value = float(value) - if value is value < 0: + if value < 0: raise AttributeError("timeout: Timeout cannot be negative") if value > self.maximum: From dafd42b2c376e003ead70ba6ba458c4db42a7b98 Mon Sep 17 00:00:00 2001 From: peace-maker Date: Sun, 29 Oct 2023 15:05:16 +0100 Subject: [PATCH 10/21] Fix tube.clean_and_log not logging buffered data (#2272) * Fix tube.clean_and_log not logging buffered data Only new data received after the `clean_and_log` call is printed while data already received and buffered earlier is not. Print the buffered data first before waiting for more so we don't miss anything. * Only show buffered data if DEBUG is off * Update CHANGELOG.md --------- Co-authored-by: Arusekk --- CHANGELOG.md | 2 ++ examples/clean_and_log.py | 30 ++++++++++++++++++------------ pwnlib/tubes/tube.py | 7 ++++++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d21ad85ee..d74239b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,8 +91,10 @@ The table below shows which release corresponds to each branch, and what date th ## 4.11.1 (`stable`) +- [#2272][2272] Fix `tube.clean_and_log` not logging buffered data - [#2281][2281] FIX: Getting right amount of data for search fix +[2272]: https://github.com/Gallopsled/pwntools/pull/2272 [2281]: https://github.com/Gallopsled/pwntools/pull/2281 ## 4.11.0 diff --git a/examples/clean_and_log.py b/examples/clean_and_log.py index a307d76a2..5e5a2493c 100644 --- a/examples/clean_and_log.py +++ b/examples/clean_and_log.py @@ -11,18 +11,24 @@ """ from pwn import * +from multiprocessing import Process -os.system('''(( -echo prefix sometext ; -echo prefix someothertext ; -echo here comes the flag ; -echo LostInTheInterTubes -) | nc -l 1337) & -''') +def submit_data(): + with context.quiet: + with listen(1337) as io: + io.wait_for_connection() + io.sendline(b'prefix sometext') + io.sendline(b'prefix someothertext') + io.sendline(b'here comes the flag') + io.sendline(b'LostInTheInterTubes') -r = remote('localhost', 1337) -atexit.register(r.clean_and_log) +if __name__ == '__main__': + p = Process(target=submit_data) + p.start() -while True: - line = r.recvline() - print(re.findall(r'^prefix (\S+)$', line)[0]) + r = remote('localhost', 1337) + atexit.register(r.clean_and_log) + + while True: + line = r.recvline() + print(re.findall(br'^prefix (\S+)$', line)[0]) diff --git a/pwnlib/tubes/tube.py b/pwnlib/tubes/tube.py index 39a27d8ca..0e5e9dab1 100644 --- a/pwnlib/tubes/tube.py +++ b/pwnlib/tubes/tube.py @@ -1034,8 +1034,13 @@ def clean_and_log(self, timeout = 0.05): b'hooray_data' >>> context.clear() """ + cached_data = self.buffer.get() + if cached_data and not self.isEnabledFor(logging.DEBUG): + with context.local(log_level='debug'): + self.debug('Received %#x bytes:' % len(cached_data)) + self.maybe_hexdump(cached_data, level=logging.DEBUG) with context.local(log_level='debug'): - return self.clean(timeout) + return cached_data + self.clean(timeout) def connect_input(self, other): """connect_input(other) From 0c1121d32c4adbdcb2036c94fce0b9b7fe28c0ed Mon Sep 17 00:00:00 2001 From: Kexuan Shen <71251878+ksshen0000@users.noreply.github.com> Date: Sun, 29 Oct 2023 09:38:57 -0500 Subject: [PATCH 11/21] FIX: Generated shebang with path to python invalid if path contains spaces (#2285) * modify script shebang to accept path with white space * add changelog for 2271 --- CHANGELOG.md | 2 ++ pwnlib/util/misc.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74239b7d..554b527d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,9 +91,11 @@ The table below shows which release corresponds to each branch, and what date th ## 4.11.1 (`stable`) +- [#2271][2271] FIX: Generated shebang with path to python invalid if path contains spaces - [#2272][2272] Fix `tube.clean_and_log` not logging buffered data - [#2281][2281] FIX: Getting right amount of data for search fix +[2271]: https://github.com/Gallopsled/pwntools/pull/2271 [2272]: https://github.com/Gallopsled/pwntools/pull/2272 [2281]: https://github.com/Gallopsled/pwntools/pull/2281 diff --git a/pwnlib/util/misc.py b/pwnlib/util/misc.py index 7fbf479ca..32aa5351b 100644 --- a/pwnlib/util/misc.py +++ b/pwnlib/util/misc.py @@ -382,7 +382,7 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr import os os.execve({argv0!r}, {argv!r}, os.environ) ''' - script = script.format(executable=sys.executable, + script = script.format(executable='/bin/env ' * (' ' in sys.executable) + sys.executable, argv=command, argv0=which(command[0])) script = script.lstrip() From 64d52b09f322a9a40da7d5588e1a3a7410880e19 Mon Sep 17 00:00:00 2001 From: Arusekk Date: Wed, 1 Nov 2023 23:26:43 +0100 Subject: [PATCH 12/21] shellcraft.aarch64: Fix atexit SEGV in loader (#2294) Fixes #2289 --- CHANGELOG.md | 2 ++ pwnlib/shellcraft/templates/aarch64/linux/loader.asm | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554b527d4..722187878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,10 +94,12 @@ The table below shows which release corresponds to each branch, and what date th - [#2271][2271] FIX: Generated shebang with path to python invalid if path contains spaces - [#2272][2272] Fix `tube.clean_and_log` not logging buffered data - [#2281][2281] FIX: Getting right amount of data for search fix +- [#2294][2294] Fix atexit SEGV in aarch64 loader [2271]: https://github.com/Gallopsled/pwntools/pull/2271 [2272]: https://github.com/Gallopsled/pwntools/pull/2272 [2281]: https://github.com/Gallopsled/pwntools/pull/2281 +[2294]: https://github.com/Gallopsled/pwntools/pull/2294 ## 4.11.0 diff --git a/pwnlib/shellcraft/templates/aarch64/linux/loader.asm b/pwnlib/shellcraft/templates/aarch64/linux/loader.asm index 7136aaedf..d6f23cd25 100644 --- a/pwnlib/shellcraft/templates/aarch64/linux/loader.asm +++ b/pwnlib/shellcraft/templates/aarch64/linux/loader.asm @@ -107,14 +107,14 @@ PT_LOAD = 1 mov x3, sp stp x2, x3, [sp, #-16]! - /* argc, argv[0], argv[1], envp */ + /* argc, argv[0], argv[1], envp; x0 must be zero! */ /* ideally these could all be empty, but unfortunately we have to keep the stack aligned. it's easier to just push an extra argument than care... */ stp x0, x1, [sp, #-16]! /* argv[1] = NULL, envp = NULL */ - mov x0, 1 - mov x1, sp - stp x0, x1, [sp, #-16]! /* argc = 1, argv[0] = "" */ + mov x2, 1 + mov x3, sp + stp x2, x3, [sp, #-16]! /* argc = 1, argv[0] = "" */ br x8 From 8d6dc0b12106f4c23ac98a2c87fa2232a4f0a1dd Mon Sep 17 00:00:00 2001 From: peace-maker Date: Tue, 14 Nov 2023 19:44:05 +0100 Subject: [PATCH 13/21] Python 2: Fix installing from source (#2298) * Python 2: Fix installing from source `python2.7 -m pip install git+https://github.com...` doesn't install any packages. The auto-discovery doesn't work for the old setuptools version used in Python 2. https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#flat-layout * Use Python 3 commandline tools in Docker images Since the Python 2 version was installed first, the Python 3 install didn't touch the existing shell script wrapper files. --- MANIFEST.in | 2 +- extra/docker/beta/Dockerfile | 2 +- extra/docker/dev/Dockerfile | 2 +- extra/docker/stable/Dockerfile | 2 +- setup.py | 5 ++--- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 8f001ea41..5327e1886 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,4 +6,4 @@ include *.md *.txt *.sh *.yml MANIFEST.in recursive-include docs *.rst *.png Makefile *.py *.txt recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh __doc__ *.mako recursive-include pwn *.py *.asm *.rst *.md *.txt *.sh -recursive-exclude *.pyc +global-exclude *.pyc diff --git a/extra/docker/beta/Dockerfile b/extra/docker/beta/Dockerfile index cbfd05632..5a83dd6fc 100644 --- a/extra/docker/beta/Dockerfile +++ b/extra/docker/beta/Dockerfile @@ -2,6 +2,6 @@ FROM pwntools/pwntools:stable USER root RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta \ - && python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta + && python3 -m pip install --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@beta RUN PWNLIB_NOTERM=1 pwn update USER pwntools diff --git a/extra/docker/dev/Dockerfile b/extra/docker/dev/Dockerfile index d5f7af8f5..77d04d331 100644 --- a/extra/docker/dev/Dockerfile +++ b/extra/docker/dev/Dockerfile @@ -2,6 +2,6 @@ FROM pwntools/pwntools:stable USER root RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev \ - && python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@dev + && python3 -m pip install --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@dev RUN PWNLIB_NOTERM=1 pwn update USER pwntools diff --git a/extra/docker/stable/Dockerfile b/extra/docker/stable/Dockerfile index 980ef3f7e..1535d4af1 100644 --- a/extra/docker/stable/Dockerfile +++ b/extra/docker/stable/Dockerfile @@ -2,6 +2,6 @@ FROM pwntools/pwntools:base USER root RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@stable \ - && python3 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@stable + && python3 -m pip install --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@stable RUN PWNLIB_NOTERM=1 pwn update USER pwntools diff --git a/setup.py b/setup.py index e6bb61286..a17090ee9 100755 --- a/setup.py +++ b/setup.py @@ -3,14 +3,12 @@ import glob import os -import platform -import subprocess import sys -import traceback from distutils.command.install import INSTALL_SCHEMES from distutils.sysconfig import get_python_inc from distutils.util import convert_path +from setuptools import find_packages from setuptools import setup # Get all template files @@ -48,6 +46,7 @@ if sys.version_info < (3, 4): import toml project = toml.load('pyproject.toml')['project'] + compat['packages'] = find_packages() compat['install_requires'] = project['dependencies'] compat['name'] = project['name'] if '--user' in sys.argv: From 2e09b7dd91df719387934e58e9defc89c8b45b74 Mon Sep 17 00:00:00 2001 From: Arusekk Date: Tue, 14 Nov 2023 20:01:02 +0100 Subject: [PATCH 14/21] Release 4.11.1 --- CHANGELOG.md | 7 +++++-- pwnlib/version.py | 2 +- setup.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 722187878..4f835c23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ The table below shows which release corresponds to each branch, and what date th | ---------------- | -------- | ---------------------- | | [4.13.0](#4130-dev) | `dev` | | [4.12.0](#4120-beta) | `beta` | -| [4.11.1](#4111-stable) | `stable` | +| [4.11.1](#4111-stable) | `stable` | Nov 14, 2023 | [4.11.0](#4110) | | Sep 15, 2023 | [4.10.0](#4100) | | May 21, 2023 | [4.9.0](#490) | | Dec 29, 2022 @@ -69,8 +69,9 @@ The table below shows which release corresponds to each branch, and what date th | [2.2.0](#220) | | Jan 5, 2015 ## 4.13.0 (`dev`) +- [#2281][2281] FIX: Getting right amount of data for search fix - +[2281]: https://github.com/Gallopsled/pwntools/pull/2281 ## 4.12.0 (`beta`) - [#2202][2202] Fix `remote` and `listen` in sagemath @@ -94,11 +95,13 @@ The table below shows which release corresponds to each branch, and what date th - [#2271][2271] FIX: Generated shebang with path to python invalid if path contains spaces - [#2272][2272] Fix `tube.clean_and_log` not logging buffered data - [#2281][2281] FIX: Getting right amount of data for search fix +- [#2287][2287] Fix `_countdown_handler` not invoking `timeout_change` - [#2294][2294] Fix atexit SEGV in aarch64 loader [2271]: https://github.com/Gallopsled/pwntools/pull/2271 [2272]: https://github.com/Gallopsled/pwntools/pull/2272 [2281]: https://github.com/Gallopsled/pwntools/pull/2281 +[2287]: https://github.com/Gallopsled/pwntools/pull/2287 [2294]: https://github.com/Gallopsled/pwntools/pull/2294 ## 4.11.0 diff --git a/pwnlib/version.py b/pwnlib/version.py index 6c387e498..efbed2e4c 100644 --- a/pwnlib/version.py +++ b/pwnlib/version.py @@ -1 +1 @@ -__version__ = '4.11.0' +__version__ = '4.11.1' diff --git a/setup.py b/setup.py index a17090ee9..65cb5c3cd 100755 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ sys.exit(-1) setup( - version = '4.11.0', + version = '4.11.1', data_files = [('pwntools-doc', glob.glob('*.md') + glob.glob('*.txt')), ], From b53d47697792c71e9c8e3261175de62b229c965a Mon Sep 17 00:00:00 2001 From: peace-maker Date: Tue, 14 Nov 2023 21:08:55 +0100 Subject: [PATCH 15/21] Add x86 CET status to checksec output (#2293) * Add x86 CET status to checksec output The ELF class now features two properties: - ibt for indirect branch tracking `-fcf-protection=branch` - shstk for shadow stack `-fcf-protection=return` Both checking the presence of the respective feature bit in the note property x86 feature segment. * Check if user shadowstack is enabled on SSH target Look for "user_shstk" in /proc/cpuinfo for ssh.checksec() * Add ELF.iter_notes and ELF.iter_properties helpers Allows to easily iterate the PT_NOTE segment and the properties in "GNU" notes. * Check for ibt on remote ssh machine * Update CHANGELOG * Suppress error when /proc/cpuinfo cannot be read --- CHANGELOG.md | 2 ++ pwnlib/elf/elf.py | 55 ++++++++++++++++++++++++++++++++++++++ pwnlib/tubes/ssh.py | 65 ++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 4 +-- 4 files changed, 123 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa421748..9c2a91d1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,8 +71,10 @@ The table below shows which release corresponds to each branch, and what date th ## 4.13.0 (`dev`) - [#2281][2281] FIX: Getting right amount of data for search fix +- [#2293][2293] Add x86 CET status to checksec output [2281]: https://github.com/Gallopsled/pwntools/pull/2281 +[2293]: https://github.com/Gallopsled/pwntools/pull/2293 ## 4.12.0 (`beta`) diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py index 4a2437206..abd6aae62 100644 --- a/pwnlib/elf/elf.py +++ b/pwnlib/elf/elf.py @@ -53,6 +53,7 @@ from elftools.elf.constants import SHN_INDICES from elftools.elf.descriptions import describe_e_type from elftools.elf.elffile import ELFFile +from elftools.elf.enums import ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS from elftools.elf.gnuversions import GNUVerDefSection from elftools.elf.relocation import RelocationSection from elftools.elf.sections import SymbolTableSection @@ -510,6 +511,29 @@ def iter_segments_by_type(self, t): if t == seg.header.p_type or t in str(seg.header.p_type): yield seg + def iter_notes(self): + """ + Yields: + All the notes in the PT_NOTE segments. Each result is a dictionary- + like object with ``n_name``, ``n_type``, and ``n_desc`` fields, amongst + others. + """ + for seg in self.iter_segments_by_type('PT_NOTE'): + for note in seg.iter_notes(): + yield note + + def iter_properties(self): + """ + Yields: + All the GNU properties in the PT_NOTE segments. Each result is a dictionary- + like object with ``pr_type``, ``pr_datasz``, and ``pr_data`` fields. + """ + for note in self.iter_notes(): + if note.n_type != 'NT_GNU_PROPERTY_TYPE_0': + continue + for prop in note.n_desc: + yield prop + def get_segment_for_address(self, address, size=1): """get_segment_for_address(address, size=1) -> Segment @@ -2060,6 +2084,12 @@ def checksec(self, banner=True, color=True): if self.ubsan: res.append("UBSAN:".ljust(10) + green("Enabled")) + + if self.shadowstack: + res.append("SHSTK:".ljust(10) + green("Enabled")) + + if self.ibt: + res.append("IBT:".ljust(10) + green("Enabled")) # Check for Linux configuration, it must contain more than # just the version. @@ -2117,6 +2147,31 @@ def ubsan(self): """:class:`bool`: Whether the current binary was built with Undefined Behavior Sanitizer (``UBSAN``).""" return any(s.startswith('__ubsan_') for s in self.symbols) + + @property + def shadowstack(self): + """:class:`bool`: Whether the current binary was built with + Shadow Stack (``SHSTK``)""" + if self.arch not in ['i386', 'amd64']: + return False + for prop in self.iter_properties(): + if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND': + continue + return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_SHSTK'] > 0 + return False + + @property + def ibt(self): + """:class:`bool`: Whether the current binary was built with + Indirect Branch Tracking (``IBT``)""" + if self.arch not in ['i386', 'amd64']: + return False + for prop in self.iter_properties(): + if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND': + continue + return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_IBT'] > 0 + return False + def _update_args(self, kw): kw.setdefault('arch', self.arch) diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py index 2216da0af..9337b27c3 100644 --- a/pwnlib/tubes/ssh.py +++ b/pwnlib/tubes/ssh.py @@ -17,6 +17,7 @@ from pwnlib import term from pwnlib.context import context, LocalContext +from pwnlib.exception import PwnlibException from pwnlib.log import Logger from pwnlib.log import getLogger from pwnlib.term import text @@ -613,6 +614,9 @@ def __init__(self, user=None, host=None, port=22, password=None, key=None, self._platform_info = {} self._aslr = None self._aslr_ulimit = None + self._cpuinfo_cache = None + self._user_shstk = None + self._ibt = None misc.mkdir_p(self._cachedir) @@ -2144,6 +2148,57 @@ def preexec(): return self._aslr_ulimit + def _cpuinfo(self): + if self._cpuinfo_cache is None: + with context.quiet: + try: + self._cpuinfo_cache = self.read('/proc/cpuinfo') + except PwnlibException: + self._cpuinfo_cache = b'' + return self._cpuinfo_cache + + @property + def user_shstk(self): + """:class:`bool`: Whether userspace shadow stack is supported on the system. + + Example: + + >>> s = ssh("travis", "example.pwnme") + >>> s.user_shstk + False + """ + if self._user_shstk is None: + if self.os != 'linux': + self.warn_once("Only Linux is supported for userspace shadow stack checks.") + self._user_shstk = False + + else: + cpuinfo = self._cpuinfo() + + self._user_shstk = b' user_shstk' in cpuinfo + return self._user_shstk + + @property + def ibt(self): + """:class:`bool`: Whether kernel indirect branch tracking is supported on the system. + + Example: + + >>> s = ssh("travis", "example.pwnme") + >>> s.ibt + False + """ + if self._ibt is None: + if self.os != 'linux': + self.warn_once("Only Linux is supported for kernel indirect branch tracking checks.") + self._ibt = False + + else: + cpuinfo = self._cpuinfo() + + self._ibt = b' ibt ' in cpuinfo or b' ibt\n' in cpuinfo + return self._ibt + def _checksec_cache(self, value=None): path = self._get_cachefile('%s-%s' % (self.host, self.port)) @@ -2180,7 +2235,15 @@ def checksec(self, banner=True): "ASLR:".ljust(10) + { True: green("Enabled"), False: red("Disabled") - }[self.aslr] + }[self.aslr], + "SHSTK:".ljust(10) + { + True: green("Enabled"), + False: red("Disabled") + }[self.user_shstk], + "IBT:".ljust(10) + { + True: green("Enabled"), + False: red("Disabled") + }[self.ibt], ] if self.aslr_ulimit: diff --git a/pyproject.toml b/pyproject.toml index 517553053..b33699307 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,8 +36,8 @@ requires-python = ">=2.7" dependencies = [ "paramiko>=1.15.2", "mako>=1.0.0", - "pyelftools>=0.24, <0.30; python_version < '3'", - "pyelftools>=0.24; python_version >= '3'", + "pyelftools>=0.28, <0.30; python_version < '3'", + "pyelftools>=0.28; python_version >= '3'", "capstone>=3.0.5rc2", # see Gallopsled/pwntools#971, Gallopsled/pwntools#1160 "ropgadget>=5.3", "pyserial>=2.7", From 2f2e0dc48c3a979b8ed440abf0aed3833ea8ed5d Mon Sep 17 00:00:00 2001 From: Arusekk Date: Tue, 14 Nov 2023 23:13:21 +0100 Subject: [PATCH 16/21] elf: Resolve more relocations into GOT entries (#2277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far only relocations pointing to external symbols were considered when filling GOT. Relocations in libc are a different thing, often found in CTF challenges (libc with partial RELRO has many interesting overridable function pointers, like strlen & strchrnul). Co-authored-by: Michał Radwański --- CHANGELOG.md | 2 ++ pwnlib/elf/elf.py | 26 +++++++++++++++++++++----- pyproject.toml | 4 ++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2a91d1f..16c45da47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,9 +70,11 @@ The table below shows which release corresponds to each branch, and what date th ## 4.13.0 (`dev`) +- [#2277][2277] elf: Resolve more relocations into GOT entries - [#2281][2281] FIX: Getting right amount of data for search fix - [#2293][2293] Add x86 CET status to checksec output +[2277]: https://github.com/Gallopsled/pwntools/pull/2277 [2281]: https://github.com/Gallopsled/pwntools/pull/2281 [2293]: https://github.com/Gallopsled/pwntools/pull/2293 diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py index abd6aae62..5c41f0f12 100644 --- a/pwnlib/elf/elf.py +++ b/pwnlib/elf/elf.py @@ -47,7 +47,7 @@ from six import BytesIO -from collections import namedtuple +from collections import namedtuple, defaultdict from elftools.elf.constants import P_FLAGS from elftools.elf.constants import SHN_INDICES @@ -55,7 +55,7 @@ from elftools.elf.elffile import ELFFile from elftools.elf.enums import ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS from elftools.elf.gnuversions import GNUVerDefSection -from elftools.elf.relocation import RelocationSection +from elftools.elf.relocation import RelocationSection, RelrRelocationSection from elftools.elf.sections import SymbolTableSection from elftools.elf.segments import InterpSegment @@ -941,15 +941,25 @@ def _populate_synthetic_symbols(self): self.symbols['got.' + symbol] = address def _populate_got(self): - """Loads the symbols for all relocations""" + """Loads the symbols for all relocations. + + >>> libc = ELF(which('bash')).libc + >>> assert 'strchrnul' in libc.got + >>> assert 'memcpy' in libc.got + >>> assert libc.got.strchrnul != libc.got.memcpy + """ # Statically linked implies no relocations, since there is no linker # Could always be self-relocating like Android's linker *shrug* if self.statically_linked: return + revsymbols = defaultdict(list) + for name, addr in self.symbols.items(): + revsymbols[addr].append(name) + for section in self.sections: # We are only interested in relocations - if not isinstance(section, RelocationSection): + if not isinstance(section, (RelocationSection, RelrRelocationSection)): continue # Only get relocations which link to another section (for symbols) @@ -961,7 +971,13 @@ def _populate_got(self): for rel in section.iter_relocations(): sym_idx = rel.entry.r_info_sym - if not sym_idx: + if not sym_idx and rel.is_RELA(): + # TODO: actually resolve relocations + relocated = rel.entry.r_addend # sufficient for now + + symnames = revsymbols[relocated] + for symname in symnames: + self.got[symname] = rel.entry.r_offset continue symbol = symbols.get_symbol(sym_idx) diff --git a/pyproject.toml b/pyproject.toml index b33699307..c241db72f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,8 +36,8 @@ requires-python = ">=2.7" dependencies = [ "paramiko>=1.15.2", "mako>=1.0.0", - "pyelftools>=0.28, <0.30; python_version < '3'", - "pyelftools>=0.28; python_version >= '3'", + "pyelftools>=0.29, <0.30; python_version < '3'", + "pyelftools>=0.29; python_version >= '3'", "capstone>=3.0.5rc2", # see Gallopsled/pwntools#971, Gallopsled/pwntools#1160 "ropgadget>=5.3", "pyserial>=2.7", From e4d3c82501c03de44458ae498a830fe66594f66d Mon Sep 17 00:00:00 2001 From: Arusekk Date: Tue, 14 Nov 2023 23:41:41 +0100 Subject: [PATCH 17/21] ssh: Fix unstable cpuinfo on dynscaled CPUs --- pwnlib/tubes/ssh.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py index 9337b27c3..5f020f4b0 100644 --- a/pwnlib/tubes/ssh.py +++ b/pwnlib/tubes/ssh.py @@ -1506,14 +1506,14 @@ def update(has, total): with open(local, 'wb') as fd: fd.write(data) - def _download_to_cache(self, remote, p): + def _download_to_cache(self, remote, p, fingerprint=True): with context.local(log_level='error'): remote = self.readlink('-f',remote) if not hasattr(remote, 'encode'): remote = remote.decode('utf-8') - fingerprint = self._get_fingerprint(remote) + fingerprint = fingerprint and self._get_fingerprint(remote) or None if fingerprint is None: local = os.path.normpath(remote) local = os.path.basename(local) @@ -1535,7 +1535,7 @@ def _download_to_cache(self, remote, p): return local - def download_data(self, remote): + def download_data(self, remote, fingerprint=True): """Downloads a file from the remote server and returns it as a string. Arguments: @@ -1556,7 +1556,7 @@ def download_data(self, remote): """ with self.progress('Downloading %r' % remote) as p: - with open(self._download_to_cache(remote, p), 'rb') as fd: + with open(self._download_to_cache(remote, p, fingerprint), 'rb') as fd: return fd.read() def download_file(self, remote, local = None): @@ -2152,7 +2152,7 @@ def _cpuinfo(self): if self._cpuinfo_cache is None: with context.quiet: try: - self._cpuinfo_cache = self.read('/proc/cpuinfo') + self._cpuinfo_cache = self.download_data('/proc/cpuinfo', fingerprint=False) except PwnlibException: self._cpuinfo_cache = b'' return self._cpuinfo_cache From bb1d16c3c64f80ec83672683715acf92d7b15535 Mon Sep 17 00:00:00 2001 From: Arusekk Date: Mon, 20 Nov 2023 15:31:42 +0100 Subject: [PATCH 18/21] rop: Make stack move faster (#2300) This defers calculation of all 'add (e|r)sp, 0xHUGE' payload until the gadget gets actually used in a chain. Fixes #2253 --- pwnlib/rop/rop.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/pwnlib/rop/rop.py b/pwnlib/rop/rop.py index 09923730f..6a57a60d1 100644 --- a/pwnlib/rop/rop.py +++ b/pwnlib/rop/rop.py @@ -717,15 +717,20 @@ def setRegisters(self, registers): name = ",".join(goodregs) stack.append((gadget.address, gadget)) for r in gadget.regs: - moved += context.bytes - if r in registers: - stack.append((registers[r], r)) - else: - stack.append((Padding('' % r), r)) + if isinstance(r, str): + if r in registers: + stack.append((registers[r], r)) + else: + stack.append((Padding('' % r), r)) + moved += context.bytes + continue + + for slot in range(moved, moved + r, context.bytes): + left = gadget.move - slot + stack.append((Padding('' % left), 'stack padding')) + moved += context.bytes - for slot in range(moved, gadget.move, context.bytes): - left = gadget.move - slot - stack.append((Padding('' % left), 'stack padding')) + assert moved == gadget.move return stack @@ -1389,9 +1394,7 @@ def __getattr__(self, k): elif add.match(insn): arg = int(add.match(insn).group(1), 16) sp_move += arg - while arg >= context.bytes: - regs.append(hex(arg)) - arg -= context.bytes + regs.append(arg) elif ret.match(insn): sp_move += context.bytes elif leave.match(insn): From 77957b4b41ada00eb21d5d591e79f336a829fdc4 Mon Sep 17 00:00:00 2001 From: Michal Ambroz <723625+xambroz@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:32:08 +0100 Subject: [PATCH 19/21] Fix SyntaxWarning in Python 3.12 (#2302) * libcdb.py - python 3.12 Python 3.12 would complain if the \d atom is not escaped in the binary string. /usr/lib/python3.12/site-packages/pwnlib/commandline/libcdb.py:224: SyntaxWarning: invalid escape sequence '\d' libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data) * Use raw string --------- Co-authored-by: peace-maker --- pwnlib/commandline/libcdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pwnlib/commandline/libcdb.py b/pwnlib/commandline/libcdb.py index 1555db2a7..b32400d2c 100644 --- a/pwnlib/commandline/libcdb.py +++ b/pwnlib/commandline/libcdb.py @@ -221,7 +221,7 @@ def main(args): exe = ELF(file, checksec=False) log.info('%s', text.red(os.path.basename(file))) - libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data) + libc_version = re.search(br'libc[ -](\d+\.\d+)', exe.data) if libc_version: log.indented('%-20s %s', text.green('Version:'), libc_version.group(1).decode()) From 4ef474624394070d82d894609c23d0a2dd60bee2 Mon Sep 17 00:00:00 2001 From: Michal Ambroz <723625+xambroz@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:37:29 +0100 Subject: [PATCH 20/21] Remove python2 shebang lines from commandline tools (#2301) * remove python2 shabangs * Remove execute bit from commandline implementation --------- Co-authored-by: Peace-Maker --- pwnlib/commandline/__init__.py | 1 - pwnlib/commandline/asm.py | 1 - pwnlib/commandline/checksec.py | 1 - pwnlib/commandline/constgrep.py | 1 - pwnlib/commandline/cyclic.py | 1 - pwnlib/commandline/debug.py | 1 - pwnlib/commandline/disablenx.py | 1 - pwnlib/commandline/disasm.py | 1 - pwnlib/commandline/elfdiff.py | 1 - pwnlib/commandline/elfpatch.py | 1 - pwnlib/commandline/hex.py | 1 - pwnlib/commandline/phd.py | 1 - pwnlib/commandline/shellcraft.py | 1 - pwnlib/commandline/template.py | 1 - pwnlib/commandline/unhex.py | 1 - 15 files changed, 15 deletions(-) mode change 100755 => 100644 pwnlib/commandline/constgrep.py mode change 100755 => 100644 pwnlib/commandline/template.py diff --git a/pwnlib/commandline/__init__.py b/pwnlib/commandline/__init__.py index a0aeedac1..2c1b31aef 100644 --- a/pwnlib/commandline/__init__.py +++ b/pwnlib/commandline/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 __all__ = [ 'asm', 'checksec', diff --git a/pwnlib/commandline/asm.py b/pwnlib/commandline/asm.py index 8f1c39884..03c51a6a2 100644 --- a/pwnlib/commandline/asm.py +++ b/pwnlib/commandline/asm.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/checksec.py b/pwnlib/commandline/checksec.py index 1b7e74c3c..5dcea5e38 100644 --- a/pwnlib/commandline/checksec.py +++ b/pwnlib/commandline/checksec.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/constgrep.py b/pwnlib/commandline/constgrep.py old mode 100755 new mode 100644 index 5959b5155..bac138d72 --- a/pwnlib/commandline/constgrep.py +++ b/pwnlib/commandline/constgrep.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/cyclic.py b/pwnlib/commandline/cyclic.py index eeb55b9b0..ff012a359 100644 --- a/pwnlib/commandline/cyclic.py +++ b/pwnlib/commandline/cyclic.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/debug.py b/pwnlib/commandline/debug.py index 5c92af36d..fe5fca6f5 100644 --- a/pwnlib/commandline/debug.py +++ b/pwnlib/commandline/debug.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/disablenx.py b/pwnlib/commandline/disablenx.py index 9751f3b6a..29839c0f8 100644 --- a/pwnlib/commandline/disablenx.py +++ b/pwnlib/commandline/disablenx.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/disasm.py b/pwnlib/commandline/disasm.py index 78e69b904..4c4535594 100644 --- a/pwnlib/commandline/disasm.py +++ b/pwnlib/commandline/disasm.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division from __future__ import print_function diff --git a/pwnlib/commandline/elfdiff.py b/pwnlib/commandline/elfdiff.py index 60e5d8fbf..48afef09f 100644 --- a/pwnlib/commandline/elfdiff.py +++ b/pwnlib/commandline/elfdiff.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/elfpatch.py b/pwnlib/commandline/elfpatch.py index 7de0f2015..10a5adc24 100644 --- a/pwnlib/commandline/elfpatch.py +++ b/pwnlib/commandline/elfpatch.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/hex.py b/pwnlib/commandline/hex.py index 136106c67..d538af246 100644 --- a/pwnlib/commandline/hex.py +++ b/pwnlib/commandline/hex.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/phd.py b/pwnlib/commandline/phd.py index 1ef1fd91a..7f3891e0f 100644 --- a/pwnlib/commandline/phd.py +++ b/pwnlib/commandline/phd.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/shellcraft.py b/pwnlib/commandline/shellcraft.py index 9d49c5608..9f5fe36ae 100644 --- a/pwnlib/commandline/shellcraft.py +++ b/pwnlib/commandline/shellcraft.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/template.py b/pwnlib/commandline/template.py old mode 100755 new mode 100644 index a8f480dfe..f68461cb6 --- a/pwnlib/commandline/template.py +++ b/pwnlib/commandline/template.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division diff --git a/pwnlib/commandline/unhex.py b/pwnlib/commandline/unhex.py index 048bb9224..a254e6b3f 100644 --- a/pwnlib/commandline/unhex.py +++ b/pwnlib/commandline/unhex.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python2 from __future__ import absolute_import from __future__ import division From 65f9d5761d1fdb7c6dc4e98cbc6ac4f221678371 Mon Sep 17 00:00:00 2001 From: Heap Crash <66139157+heapcrash@users.noreply.github.com> Date: Fri, 24 Nov 2023 07:02:53 -0600 Subject: [PATCH 21/21] Allow to add to the existing environment in `process` instead of replacing it (#1763) * Add env_add to the process module * Don't mutate passed `env` parameter dictionary Co-authored-by: Arusekk * Update CHANGELOG * Use a boolean flag instead of env_add There should be preferably only one obvious way to pass env. * Update CHANGELOG --------- Co-authored-by: peace-maker Co-authored-by: peace-maker Co-authored-by: Arusekk --- CHANGELOG.md | 2 ++ pwnlib/tubes/process.py | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c45da47..cd4c90007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,10 +73,12 @@ The table below shows which release corresponds to each branch, and what date th - [#2277][2277] elf: Resolve more relocations into GOT entries - [#2281][2281] FIX: Getting right amount of data for search fix - [#2293][2293] Add x86 CET status to checksec output +- [#1763][1763] Allow to add to the existing environment in `process` instead of replacing it [2277]: https://github.com/Gallopsled/pwntools/pull/2277 [2281]: https://github.com/Gallopsled/pwntools/pull/2281 [2293]: https://github.com/Gallopsled/pwntools/pull/2293 +[1763]: https://github.com/Gallopsled/pwntools/pull/1763 ## 4.12.0 (`beta`) diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py index 8770ade0c..8c44e7b7d 100644 --- a/pwnlib/tubes/process.py +++ b/pwnlib/tubes/process.py @@ -58,7 +58,9 @@ class process(tube): cwd(str): Working directory. Uses the current working directory by default. env(dict): - Environment variables. By default, inherits from Python's environment. + Environment variables to add to the environment. + ignore_environ(bool): + Ignore Python's environment. By default use Python's environment iff env not specified. stdin(int): File object or file descriptor number to use for ``stdin``. By default, a pipe is used. A pty can be used instead by setting @@ -224,6 +226,7 @@ def __init__(self, argv = None, executable = None, cwd = None, env = None, + ignore_environ = None, stdin = PIPE, stdout = PTY, stderr = STDOUT, @@ -255,7 +258,7 @@ def __init__(self, argv = None, original_env = env if shell: - executable_val, argv_val, env_val = executable, argv, env + executable_val, argv_val, env_val = executable or '/bin/sh', argv, env else: executable_val, argv_val, env_val = self._validate(cwd, executable, argv, env) @@ -287,14 +290,14 @@ def __init__(self, argv = None, #: Full path to the executable self.executable = executable_val + if ignore_environ is None: + ignore_environ = env is not None # compat + #: Environment passed on envp - self.env = os.environ if env is None else env_val + self.env = {} if ignore_environ else dict(getattr(os, "environb", os.environ)) - if self.executable is None: - if shell: - self.executable = '/bin/sh' - else: - self.executable = which(self.argv[0], path=self.env.get('PATH')) + # Add environment variables as needed + self.env.update(env_val or {}) self._cwd = os.path.realpath(cwd or os.path.curdir)