├── .codeclimate.yml ├── .github ├── dependabot.yml └── workflows │ ├── install.sh │ └── ruby.yml ├── .gitignore ├── .pullapprove.yml ├── .rubocop.yml ├── .yardopts ├── Gemfile ├── LICENSE ├── LICENSE-pwntools-python.txt ├── README.md ├── Rakefile ├── STYLE.md ├── appveyor.yml ├── git-hooks └── pre-push ├── lib ├── pwn.rb └── pwnlib │ ├── abi.rb │ ├── asm.rb │ ├── constants │ ├── LICENSE.txt │ ├── constant.rb │ ├── constants.rb │ └── linux │ │ ├── amd64.rb │ │ └── i386.rb │ ├── context.rb │ ├── dynelf.rb │ ├── elf │ └── elf.rb │ ├── errors.rb │ ├── ext │ ├── array.rb │ ├── helper.rb │ ├── integer.rb │ └── string.rb │ ├── logger.rb │ ├── memleak.rb │ ├── pwn.rb │ ├── reg_sort.rb │ ├── runner.rb │ ├── shellcraft │ ├── generators │ │ ├── amd64 │ │ │ ├── common │ │ │ │ ├── common.rb │ │ │ │ ├── infloop.rb │ │ │ │ ├── memcpy.rb │ │ │ │ ├── mov.rb │ │ │ │ ├── nop.rb │ │ │ │ ├── popad.rb │ │ │ │ ├── pushstr.rb │ │ │ │ ├── pushstr_array.rb │ │ │ │ ├── ret.rb │ │ │ │ └── setregs.rb │ │ │ └── linux │ │ │ │ ├── cat.rb │ │ │ │ ├── execve.rb │ │ │ │ ├── exit.rb │ │ │ │ ├── linux.rb │ │ │ │ ├── ls.rb │ │ │ │ ├── open.rb │ │ │ │ ├── sh.rb │ │ │ │ ├── sleep.rb │ │ │ │ └── syscall.rb │ │ ├── helper.rb │ │ ├── i386 │ │ │ ├── common │ │ │ │ ├── common.rb │ │ │ │ ├── infloop.rb │ │ │ │ ├── memcpy.rb │ │ │ │ ├── mov.rb │ │ │ │ ├── nop.rb │ │ │ │ ├── pushstr.rb │ │ │ │ ├── pushstr_array.rb │ │ │ │ └── setregs.rb │ │ │ └── linux │ │ │ │ ├── cat.rb │ │ │ │ ├── execve.rb │ │ │ │ ├── exit.rb │ │ │ │ ├── linux.rb │ │ │ │ ├── ls.rb │ │ │ │ ├── open.rb │ │ │ │ ├── sh.rb │ │ │ │ ├── sleep.rb │ │ │ │ └── syscall.rb │ │ └── x86 │ │ │ ├── common │ │ │ ├── common.rb │ │ │ ├── infloop.rb │ │ │ ├── memcpy.rb │ │ │ ├── mov.rb │ │ │ ├── pushstr.rb │ │ │ ├── pushstr_array.rb │ │ │ └── setregs.rb │ │ │ └── linux │ │ │ ├── cat.rb │ │ │ ├── execve.rb │ │ │ ├── exit.rb │ │ │ ├── linux.rb │ │ │ ├── ls.rb │ │ │ ├── open.rb │ │ │ ├── sh.rb │ │ │ ├── sleep.rb │ │ │ └── syscall.rb │ ├── registers.rb │ └── shellcraft.rb │ ├── timer.rb │ ├── tubes │ ├── buffer.rb │ ├── process.rb │ ├── serialtube.rb │ ├── sock.rb │ └── tube.rb │ ├── ui.rb │ ├── util │ ├── cyclic.rb │ ├── fiddling.rb │ ├── getdents.rb │ ├── hexdump.rb │ ├── lists.rb │ ├── packing.rb │ └── ruby.rb │ └── version.rb ├── pwntools.gemspec ├── tasks └── shellcraft │ └── x86.rake └── test ├── abi_test.rb ├── asm_test.rb ├── constants ├── constant_test.rb └── constants_test.rb ├── context_test.rb ├── data ├── assembly │ ├── aarch64.s │ ├── amd64.s │ ├── arm.s │ ├── i386.s │ ├── mips.s │ ├── mips64.s │ ├── powerpc.s │ ├── powerpc64.s │ ├── sparc.s │ ├── sparc64.s │ └── thumb.s ├── echo.rb ├── elfs │ ├── Makefile │ ├── amd64.frelro.elf │ ├── amd64.frelro.pie.elf │ ├── amd64.nrelro.elf │ ├── amd64.prelro.elf │ ├── amd64.static.elf │ ├── i386.frelro.pie.elf │ ├── i386.prelro.elf │ └── source.cpp ├── flag ├── lib32 │ ├── ld.so.2 │ └── libc.so.6 ├── lib64 │ ├── ld.so.2 │ └── libc.so.6 ├── victim.c ├── victim32 └── victim64 ├── dynelf_test.rb ├── elf └── elf_test.rb ├── ext_test.rb ├── files ├── use_pwn.rb └── use_pwnlib.rb ├── full_file_test.rb ├── logger_test.rb ├── memleak_test.rb ├── reg_sort_test.rb ├── runner_test.rb ├── shellcraft ├── infloop_test.rb ├── linux │ ├── cat_test.rb │ ├── ls_test.rb │ ├── sh_test.rb │ ├── sleep_test.rb │ └── syscalls │ │ ├── execve_test.rb │ │ ├── exit_test.rb │ │ ├── open_test.rb │ │ └── syscall_test.rb ├── memcpy_test.rb ├── mov_test.rb ├── nop_test.rb ├── popad_test.rb ├── pushstr_array_test.rb ├── pushstr_test.rb ├── registers_test.rb ├── ret_test.rb ├── setregs_test.rb └── shellcraft_test.rb ├── test_helper.rb ├── timer_test.rb ├── tubes ├── buffer_test.rb ├── process_test.rb ├── serialtube_test.rb ├── sock_test.rb └── tube_test.rb ├── ui_test.rb └── util ├── cyclic_test.rb ├── fiddling_test.rb ├── getdents_test.rb ├── hexdump_test.rb ├── lists_test.rb └── packing_test.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "2" 3 | plugins: 4 | duplication: 5 | enabled: true 6 | exclude_patterns: 7 | - lib/pwnlib/shellcraft/generators/ 8 | - test/ 9 | checks: 10 | file-lines: 11 | enabled: false 12 | method-lines: 13 | enabled: false 14 | method-complexity: 15 | config: 16 | threshold: 10 17 | exclude_patterns: 18 | - test/ 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: rubocop 10 | versions: 11 | - 1.12.1 12 | -------------------------------------------------------------------------------- /.github/workflows/install.sh: -------------------------------------------------------------------------------- 1 | set -e -x 2 | 3 | install_keystone_from_source() 4 | { 5 | # keystone can only be built from source 6 | # https://github.com/keystone-engine/keystone/blob/master/docs/COMPILE-NIX.md 7 | # 8 | # XXX(david942j): How to prevent it from being compiled every time? 9 | git clone https://github.com/keystone-engine/keystone.git 10 | # rvm does lots of things on OSX when cwd changing.. use bash without rvm to prevent this. 11 | /bin/bash --norc -c 'mkdir keystone/build && cd keystone/build && ../make-share.sh' 12 | } 13 | 14 | setup_linux() 15 | { 16 | sudo apt update 17 | sudo apt install --force-yes gcc-multilib g++-multilib binutils socat libcapstone3 18 | 19 | # install keystone 20 | install_keystone_from_source 21 | } 22 | 23 | setup_osx() 24 | { 25 | # install capstone 26 | brew install capstone 27 | 28 | # install keystone 29 | install_keystone_from_source 30 | # hack, don't know why set DYLD_LIBRARY_PATH has no effect 31 | ln -s keystone/build/llvm/lib/libkeystone.dylib libkeystone.dylib 32 | 33 | # install socat 34 | brew install socat 35 | } 36 | 37 | if [[ "$1" == "macOS" ]]; then 38 | setup_osx 39 | elif [[ "$1" == "Linux" ]]; then 40 | setup_linux 41 | fi 42 | 43 | set +e +x 44 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ${{ matrix.os }}-latest 13 | strategy: 14 | matrix: 15 | os: [ubuntu, macos] 16 | ruby-version: ['2.6', '2.7', head] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Ruby 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: ${{ matrix.ruby-version }} 24 | bundler-cache: true 25 | - name: Install packages 26 | run: .github/workflows/install.sh ${{ runner.os }} 27 | - name: Run tests 28 | run: bundle exec rake 29 | env: 30 | LD_LIBRARY_PATH: ./keystone/build/llvm/lib 31 | DYLD_LIBRARY_PATH: /usr/local/opt/capstone/lib 32 | - name: Publish code coverage to codeclimate 33 | if: ${{ success() && runner.os == 'Linux' && env.CC_TEST_REPORTER_ID }} 34 | uses: paambaati/codeclimate-action@v2.7.5 35 | env: 36 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | ## Specific to RubyMotion: 14 | .dat* 15 | .repl_history 16 | build/ 17 | 18 | ## Documentation cache and generated files: 19 | /.yardoc/ 20 | /_yardoc/ 21 | /doc/ 22 | /rdoc/ 23 | 24 | ## Environment normalization: 25 | /.bundle/ 26 | /vendor/bundle 27 | /lib/bundler/man/ 28 | 29 | # for a library or gem, you might want to ignore these files since the code is 30 | # intended to run in multiple environments; otherwise, check them in: 31 | # Gemfile.lock 32 | # .ruby-version 33 | # .ruby-gemset 34 | 35 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 36 | .rvmrc 37 | 38 | Gemfile.lock 39 | -------------------------------------------------------------------------------- /.pullapprove.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | groups: 3 | code-review: 4 | approve_by_comment: 5 | approve_regex: '^(Approved|:\+1:|LGTM|:lgtm:)' 6 | enabled: true 7 | required: 1 8 | reset_on_push: 9 | enabled: true 10 | users: 11 | - peter50216 12 | - david942j 13 | - ShikChen 14 | - hanhanW 15 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | DisplayStyleGuide: true 4 | TargetRubyVersion: 2.5 5 | 6 | Layout/EndOfLine: 7 | EnforcedStyle: lf 8 | 9 | Layout/HeredocIndentation: 10 | Enabled: false 11 | 12 | Layout/LineLength: 13 | Enabled: true 14 | Max: 120 15 | 16 | Lint/ConstantDefinitionInBlock: 17 | Enabled: true 18 | Exclude: 19 | - 'tasks/**/*.rake' 20 | 21 | Metrics/AbcSize: 22 | Enabled: false 23 | 24 | Metrics/BlockLength: 25 | Enabled: false 26 | 27 | Metrics/ClassLength: 28 | Enabled: false 29 | 30 | Metrics/CyclomaticComplexity: 31 | Enabled: false 32 | 33 | Metrics/MethodLength: 34 | Enabled: false 35 | 36 | Metrics/ModuleLength: 37 | Enabled: false 38 | 39 | Metrics/ParameterLists: 40 | Enabled: false 41 | 42 | Metrics/PerceivedComplexity: 43 | Enabled: false 44 | 45 | Naming/HeredocDelimiterNaming: 46 | Enabled: false 47 | 48 | Naming/MethodParameterName: 49 | Enabled: false 50 | 51 | Naming/VariableNumber: 52 | Enabled: false 53 | 54 | Style/AsciiComments: 55 | Enabled: false 56 | 57 | Style/AutoResourceCleanup: 58 | Enabled: true 59 | 60 | Style/CollectionMethods: 61 | Enabled: true 62 | 63 | Style/Documentation: 64 | Enabled: true 65 | Exclude: 66 | - 'lib/pwnlib/shellcraft/generators/**/*.rb' 67 | - 'test/**/*.rb' 68 | 69 | Style/Encoding: 70 | Enabled: false 71 | 72 | Style/FormatStringToken: 73 | Enabled: false 74 | 75 | # TODO(david942j): 76 | # `yield` cannot be ignored (didn't track why). 77 | # Enable this cop whenever its related bugs are fixed. 78 | # Style/MethodCallWithArgsParentheses: 79 | # Enabled: true 80 | # IgnoredMethods: 81 | # - require 82 | # - yield 83 | # - skip 84 | # - raise 85 | 86 | Style/MethodCalledOnDoEndBlock: 87 | Enabled: true 88 | 89 | Style/MixinUsage: 90 | Enabled: true 91 | Exclude: 92 | - test/**/*.rb 93 | - lib/pwn.rb 94 | 95 | Style/OptionHash: 96 | Enabled: true 97 | 98 | Style/PercentLiteralDelimiters: 99 | Enabled: false 100 | 101 | Style/Send: 102 | Enabled: true 103 | 104 | Style/StringMethods: 105 | Enabled: true 106 | 107 | Style/SymbolArray: 108 | Enabled: true 109 | 110 | Style/SingleLineBlockParams: 111 | Enabled: true 112 | 113 | Style/YodaCondition: 114 | Enabled: false 115 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --tag diff:"Difference with Python pwntools" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://www.rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-pwntools-python.txt: -------------------------------------------------------------------------------- 1 | TL;DR version: 2 | Everything in pwntools is open source. Most is under an MIT license, but a 3 | few pieces are under GPL or a BSD 2-clause licence. 4 | 5 | This license covers everything within this project, except for a few pieces 6 | of code that we either did not write ourselves or which we derived from code 7 | that we did not write ourselves. These few pieces have their license specified 8 | in a header, or by a file called LICENSE.txt, which will explain exactly what 9 | it covers. The few relevant pieces of code are all contained inside these 10 | directories: 11 | 12 | - pwnlib/constants/ 13 | - pwnlib/data/ 14 | 15 | 16 | Copyright (c) 2015 Gallopsled and Zach Riggle 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'pathname' 3 | 4 | require 'bundler/gem_tasks' 5 | require 'rainbow' 6 | require 'rake/testtask' 7 | require 'rubocop/rake_task' 8 | require 'yard' 9 | 10 | import 'tasks/shellcraft/x86.rake' 11 | 12 | RuboCop::RakeTask.new(:rubocop) do |task| 13 | task.patterns = ['lib/**/*.rb', 'tasks/**/*.rake', 'test/**/*.rb'] 14 | end 15 | 16 | task default: %i(install_git_hooks rubocop test) 17 | 18 | Rake::TestTask.new(:test) do |test| 19 | test.libs << 'lib' 20 | test.libs << 'test' 21 | test.pattern = 'test/**/*_test.rb' 22 | test.verbose = true 23 | test.options = '--pride' 24 | end 25 | 26 | YARD::Rake::YardocTask.new(:doc) 27 | 28 | task :install_git_hooks do 29 | next if ENV['CI'] 30 | hooks = %w(pre-push) 31 | git_hook_dir = Pathname.new('.git/hooks/') 32 | hook_dir = Pathname.new('git-hooks/') 33 | hooks.each do |hook| 34 | src = hook_dir + hook 35 | target = git_hook_dir + hook 36 | next if target.symlink? && (target.dirname + target.readlink).realpath == src.realpath 37 | puts "Installing git hook #{hook}..." 38 | target.unlink if target.exist? || target.symlink? 39 | target.make_symlink(src.relative_path_from(target.dirname)) 40 | end 41 | git_version = `git version`[/\Agit version (.*)\Z/, 1] 42 | if Gem::Version.new(git_version) < Gem::Version.new('1.8.2') 43 | puts Rainbow("Your git is older than 1.8.2, and doesn't support pre-push hook...").bright.red 44 | puts Rainbow('Please make sure test passed before pushing!!!!!!').bright.red 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /STYLE.md: -------------------------------------------------------------------------------- 1 | # Coding style / document style guideline. 2 | 3 | We follow [rubocop](https://github.com/bbatsov/rubocop) for most of the things. 4 | See `.rubocop.yml` for the configuration. 5 | 6 | Results of some discussion of styles are put here, 7 | so we can have consistent style over the code base. 8 | 9 | ## `(a..b)` or `a.upto(b)` 10 | ```ruby 11 | # Good 12 | (a..b).map { ... } 13 | (a...b).map { ... } 14 | a.upto(b) { ... } 15 | b.downto(a) { ... } 16 | 17 | # Bad 18 | (a..b).each { ... } 19 | (a..b).reverse_each { ... } 20 | a.upto(b).map { ... } 21 | ``` 22 | 23 | ## YARD tag order 24 | Tag order is `@param, @return, @yieldparam, @yieldreturn, @raise, @todo, @note, @diff, @example`. 25 | 26 | No new line between tags of the same kind, 27 | a new line between tags of different kind, 28 | and a new line between the document string and the first tag. 29 | 30 | ## `require` order 31 | Four groups: Ruby stdlib, other dependencies, `test_helper` (for tests), our library. 32 | 33 | A new line between each group, 34 | and sort alphabetically in each group. 35 | 36 | Example for header: 37 | ```ruby 38 | # encoding: ASCII-8BIT 39 | 40 | require 'open3' 41 | require 'socket' 42 | 43 | require 'dentaku' 44 | require 'rainbow' 45 | 46 | require 'test_helper' 47 | 48 | require 'pwnlib/context' 49 | require 'pwnlib/version' 50 | ``` 51 | 52 | ## Document style 53 | * Public methods intended to be used by user should be documented. 54 | * `@param`, `@return` document on a indented new line. 55 | ```ruby 56 | # Good 57 | # @param [String] bla 58 | # A bla argument. 59 | 60 | # Bad 61 | # @param [String] bla A bla argument. 62 | ``` 63 | 64 | ## Comment style 65 | ```ruby 66 | # Good 67 | # TODO(myname): A single line todo. 68 | 69 | # TODO(myname): 70 | # A multiple line and long long long looooooooooooooooooooooong todo, 71 | # Second line!!!!! 72 | 73 | # Bad 74 | # TODO(myname): A multiple line and long long long looooooooooooooooooooooong todo, 75 | # Second line!!!!! 76 | ``` 77 | * Use `@todo` tag if it's worthy to show the todo in document, use `# TODO` elsewhere. 78 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build}-{branch} 2 | 3 | environment: 4 | matrix: 5 | - RUBY_VERSION: '26' 6 | - RUBY_VERSION: '26-x64' 7 | 8 | install: 9 | - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH% 10 | - gem update --system 11 | - gem install bundler 12 | - gem --version 13 | - bundle install 14 | 15 | build: off 16 | 17 | branches: 18 | only: 19 | - master 20 | 21 | before_test: 22 | - bundle exec ruby -v 23 | - bundle exec gem -v 24 | - bundle -v 25 | 26 | test_script: 27 | - bundle exec rake 28 | -------------------------------------------------------------------------------- /git-hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | die() { 4 | IFS='' 5 | echo -e "\e[1;31m$*\e[m" 6 | exit 1 7 | } 8 | 9 | check_no_push_master() { 10 | # This actually only checks local branch, not remote branch to be pushed. 11 | # Should be good enough for now. 12 | if [[ "$(git symbolic-ref HEAD)" == "refs/heads/master" ]]; then 13 | die "Trying to push to master branch... Please don't do this.\n" \ 14 | "Create a branch, pull request and squash merge for better commit " \ 15 | "message on master." 16 | fi 17 | } 18 | 19 | check_rake_passed() { 20 | local git_status="$(git status --porcelain)" 21 | if [[ ! -z "${git_status}" ]]; then 22 | echo "Stashing all local changes. If the script is interrupted, you have to" \ 23 | "do git stash pop by yourself." 24 | git stash save -q --include-untracked || die "stash error QQ" 25 | fi 26 | bundle exec rake 27 | local rake_status=$? 28 | if [[ ! -z "${git_status}" ]]; then 29 | git stash pop -q || die "stash pop error QQ" 30 | fi 31 | 32 | if [[ "${rake_status}" != "0" ]]; then 33 | die "Rake failed! Fix those errors before pushing and make sure you've " \ 34 | "committed all changes needed!" 35 | exit 1 36 | fi 37 | } 38 | 39 | main() { 40 | echo "running pre-push hooks..." 41 | 42 | check_no_push_master 43 | check_rake_passed 44 | 45 | echo "pre-push hooks done!" 46 | } 47 | 48 | main 49 | -------------------------------------------------------------------------------- /lib/pwn.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | # require this file for easy exploit development, but would pollute main Object and some built-in objects. (String, 5 | # Integer, ...) 6 | 7 | require 'pwnlib/pwn' 8 | 9 | require 'pwnlib/ext/string' 10 | require 'pwnlib/ext/integer' 11 | require 'pwnlib/ext/array' 12 | 13 | extend Pwn 14 | 15 | include Pwnlib 16 | include Pwnlib::Tubes 17 | 18 | # XXX(david942j): include here because module ELF and class ELF have same name.. 19 | include ::Pwnlib::ELF 20 | 21 | # Small "fix" for irb context problem. 22 | # irb defines main.context for IRB::Context, which overrides our Pwnlib::Context. :( 23 | # Since our "context" should be more important for someone requiring 'pwn', and the IRB::Context can still be accessible 24 | # from irb_context, we should be fine removing context. 25 | class << self 26 | remove_method(:context) if method_defined?(:context) 27 | end 28 | -------------------------------------------------------------------------------- /lib/pwnlib/abi.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/context' 5 | 6 | module Pwnlib 7 | # Encapsulates information about a calling convention. 8 | module ABI 9 | # A super class for recording registers and stack's information. 10 | class ABI 11 | attr_reader :register_arguments, :arg_alignment, :stack_pointer 12 | # Only used for x86, to specify the +eax+, +edx+ pair. 13 | attr_reader :cdq_pair 14 | 15 | def initialize(regs, align, stack_pointer, cdq_pair: nil) 16 | @register_arguments = regs 17 | @arg_alignment = align 18 | @stack_pointer = stack_pointer 19 | @cdq_pair = cdq_pair 20 | end 21 | 22 | class << self 23 | def default 24 | DEFAULT[arch_key] 25 | end 26 | 27 | def syscall 28 | SYSCALL[arch_key] 29 | end 30 | 31 | private 32 | 33 | def arch_key 34 | [context.bits, context.arch, context.os] 35 | end 36 | include ::Pwnlib::Context 37 | end 38 | end 39 | 40 | # The syscall ABI treats the syscall number as the zeroth argument, 41 | # which must be loaded into the specified register. 42 | class SyscallABI < ABI 43 | attr_reader :syscall_str 44 | 45 | def initialize(regs, align, stack_pointer, syscall_str) 46 | super(regs, align, stack_pointer) 47 | @syscall_str = syscall_str 48 | end 49 | end 50 | 51 | DEFAULT = { 52 | [32, 'i386', 'linux'] => ABI.new([], 4, 'esp', cdq_pair: %w(eax edx)), 53 | [64, 'amd64', 'linux'] => ABI.new(%w(rdi rsi rdx rcx r8 r9), 8, 'rsp', cdq_pair: %w(rax rdx)) 54 | }.freeze 55 | 56 | SYSCALL = { 57 | [32, 'i386', 'linux'] => SyscallABI.new(%w(eax ebx ecx edx esi edi ebp), 4, 'esp', 'int 0x80'), 58 | [64, 'amd64', 'linux'] => SyscallABI.new(%w(rax rdi rsi rdx r10 r8 r9), 8, 'rsp', 'syscall') 59 | }.freeze 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/pwnlib/constants/LICENSE.txt: -------------------------------------------------------------------------------- 1 | This license is modified from https://github.com/Gallopsled/pwntools/blob/dev/pwnlib/data/includes/LICENSE.txt. 2 | Remove terms related to FreeBSD since we don't have it yet. 3 | 4 | Files in templates/ and its subdirectories contain: 5 | 6 | - header files from the dietlibc project 7 | - code for transforming these header files into a 8 | format more suitable for our use of them 9 | - the result of that transformation 10 | 11 | The header files can be retrieved from http://www.fefe.de/dietlibc/ respectively. 12 | 13 | The header files from dietlibc are to the best of our knowledge 14 | available under under GPLv2 or later, available here: 15 | 16 | https://www.gnu.org/copyleft/gpl.html 17 | 18 | We are not laywers and do not know the legal status of header files nor do we 19 | know the legal status of derivatives. To the extend that we can legally do so, 20 | we would like to redistribute everything under an MIT license, but otherwise 21 | we allow it under the licenses the files were originally made available under. 22 | -------------------------------------------------------------------------------- /lib/pwnlib/constants/constant.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/util/fiddling' 5 | 6 | module Pwnlib 7 | module Constants 8 | # A class that includes name and value representing a constant. 9 | # This class works like an integer, and support operations with integers. 10 | # 11 | # @example 12 | # a = Pwnlib::Constants::Constant.new('a', 0x3) 13 | # #=> Constant("a", 0x3) 14 | # [a + 1, 2 * a, a | 6, a == 3, 0 > a] 15 | # #=> [4, 6, 7, true, false] 16 | class Constant < Numeric 17 | # @return [String] 18 | attr_reader :str 19 | 20 | # @return [Integer] 21 | attr_reader :val 22 | 23 | # @param [String] str 24 | # @param [Integer] val 25 | def initialize(str, val) 26 | super() 27 | @str = str 28 | @val = val 29 | end 30 | 31 | # We don't need to fall back to super for this, so just disable the lint. 32 | def method_missing(method, *args, &block) 33 | @val.__send__(method, *args, &block) 34 | end 35 | 36 | def respond_to_missing?(method, include_all) 37 | @val.respond_to?(method, include_all) 38 | end 39 | 40 | def coerce(other) 41 | [other.to_i, to_i] 42 | end 43 | 44 | def to_i 45 | @val 46 | end 47 | 48 | def to_s 49 | @str 50 | end 51 | 52 | def inspect 53 | format('Constant(%p, %s)', @str, ::Pwnlib::Util::Fiddling.hex(@val)) 54 | end 55 | 56 | def <=>(other) 57 | to_i <=> other.to_i 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/pwnlib/constants/constants.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'dentaku' 5 | 6 | require 'pwnlib/constants/constant' 7 | require 'pwnlib/context' 8 | require 'pwnlib/errors' 9 | 10 | module Pwnlib 11 | # Module containing constants. 12 | # 13 | # @example 14 | # context.arch = 'amd64' 15 | # Pwnlib::Constants.SYS_read 16 | # #=> Constant('SYS_read', 0x0) 17 | module Constants 18 | class << self 19 | # To support getting constants like +Pwnlib::Constants.SYS_read+. 20 | # 21 | # @return [Constant] 22 | # 23 | # @raise [NoMethodError] 24 | def method_missing(method, *args, &block) 25 | args.empty? && block.nil? && get_constant(method) || super 26 | end 27 | 28 | # @return [Boolean] 29 | def respond_to_missing?(method, _include_all) 30 | !get_constant(method).nil? 31 | end 32 | 33 | # Eval for Constants. 34 | # 35 | # @param [String] str 36 | # The string to be evaluated. 37 | # 38 | # @return [Constant] 39 | # The evaluated result. 40 | # 41 | # @raise [Pwnlib::Errors::ConstantNotFoundError] 42 | # Raised when undefined constant(s) provided. 43 | # 44 | # @example 45 | # Constants.eval('O_CREAT') 46 | # #=> Constant('(O_CREAT)', 0x40) 47 | # Constants.eval('O_CREAT | O_APPEND') 48 | # #=> Constant('(O_CREAT | O_APPEND)', 0x440) 49 | # @example 50 | # Constants.eval('meow') 51 | # # Pwnlib::Errors::ConstantNotFoundError: Undefined constant(s): meow 52 | def eval(str) 53 | return str unless str.instance_of?(String) 54 | 55 | begin 56 | val = calculator.evaluate!(str.strip).to_i 57 | rescue Dentaku::UnboundVariableError => e 58 | raise ::Pwnlib::Errors::ConstantNotFoundError, "Undefined constant(s): #{e.unbound_variables.join(', ')}" 59 | end 60 | ::Pwnlib::Constants::Constant.new("(#{str})", val) 61 | end 62 | 63 | private 64 | 65 | def current_arch_key 66 | [context.os, context.arch] 67 | end 68 | 69 | ENV_STORE = {} # rubocop:disable Style/MutableConstant 70 | def current_store 71 | ENV_STORE[current_arch_key] ||= load_constants(current_arch_key) 72 | end 73 | 74 | def get_constant(symbol) 75 | current_store[symbol] 76 | end 77 | 78 | CALCULATORS = {} # rubocop:disable Style/MutableConstant 79 | def calculator 80 | CALCULATORS[current_arch_key] ||= Dentaku::Calculator.new.store(current_store) 81 | end 82 | 83 | # Small class for instance_eval loaded file. 84 | class ConstantBuilder 85 | attr_reader :tbl 86 | 87 | def initialize 88 | @tbl = {} 89 | end 90 | 91 | def const(sym, val) 92 | @tbl[sym.to_sym] = Constant.new(sym.to_s, val) 93 | end 94 | end 95 | 96 | def load_constants((os, arch)) 97 | filename = File.join(__dir__, os, "#{arch}.rb") 98 | return {} unless File.exist?(filename) 99 | 100 | builder = ConstantBuilder.new 101 | builder.instance_eval(IO.read(filename)) 102 | builder.tbl 103 | end 104 | 105 | include ::Pwnlib::Context 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/pwnlib/errors.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | module Pwnlib 5 | # Generic {Pwnlib} exception class. 6 | class Error < StandardError 7 | end 8 | 9 | # {Pwnlib} Errors. 10 | module Errors 11 | # Raised by some IO operations in tubes. 12 | class EndOfTubeError < ::Pwnlib::Error 13 | end 14 | 15 | # Raised when a dependent file fails to load. 16 | class DependencyError < ::Pwnlib::Error 17 | end 18 | 19 | # Raised when a given constant is invalid or undefined. 20 | class ConstantNotFoundError < ::Pwnlib::Error 21 | end 22 | 23 | # Raised when timeout exceeded. 24 | class TimeoutError < ::Pwnlib::Error 25 | end 26 | 27 | # Raised when a method is not supported under current architecture. 28 | class UnsupportedArchError < ::Pwnlib::Error 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/pwnlib/ext/array.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/ext/helper' 5 | require 'pwnlib/util/fiddling' 6 | require 'pwnlib/util/packing' 7 | 8 | module Pwnlib 9 | module Ext 10 | module Array 11 | # Methods to be mixed into Array. 12 | module InstanceMethods 13 | extend ::Pwnlib::Ext::Helper 14 | 15 | def_proxy_method ::Pwnlib::Util::Packing, %w(flat) 16 | def_proxy_method ::Pwnlib::Util::Fiddling, %w(unbits) 17 | end 18 | end 19 | end 20 | end 21 | 22 | ::Array.include ::Pwnlib::Ext::Array::InstanceMethods 23 | -------------------------------------------------------------------------------- /lib/pwnlib/ext/helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | module Pwnlib 5 | module Ext 6 | # Helper methods for defining extension. 7 | module Helper 8 | def def_proxy_method(mod, *ms, **m2) 9 | ms.flatten 10 | .map { |x| [x, x] } 11 | .concat(m2.to_a) 12 | .each do |method, proxy_to| 13 | class_eval(<<-EOS, __FILE__, __LINE__ + 1) 14 | def #{method}(*args, **kwargs, &block) 15 | #{mod}.#{proxy_to}(self, *args, **kwargs, &block) 16 | end 17 | EOS 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pwnlib/ext/integer.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/ext/helper' 5 | require 'pwnlib/util/packing' 6 | 7 | module Pwnlib 8 | module Ext 9 | module Integer 10 | # Methods to be mixed into Integer. 11 | module InstanceMethods 12 | extend ::Pwnlib::Ext::Helper 13 | 14 | def_proxy_method ::Pwnlib::Util::Packing, %w(pack p8 p16 p32 p64) 15 | def_proxy_method ::Pwnlib::Util::Fiddling, %w(bits bits_str), bitswap: 'bitswap_int' 16 | def_proxy_method ::Pwnlib::Util::Fiddling, %w(hex) 17 | end 18 | end 19 | end 20 | end 21 | 22 | ::Integer.include ::Pwnlib::Ext::Integer::InstanceMethods 23 | -------------------------------------------------------------------------------- /lib/pwnlib/ext/string.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/ext/helper' 5 | require 'pwnlib/util/fiddling' 6 | require 'pwnlib/util/packing' 7 | 8 | module Pwnlib 9 | module Ext 10 | module String 11 | # Methods to be mixed into String. 12 | module InstanceMethods 13 | extend ::Pwnlib::Ext::Helper 14 | 15 | def_proxy_method ::Pwnlib::Util::Packing, %w(unpack unpack_many u8 u16 u32 u64) 16 | def_proxy_method ::Pwnlib::Util::Fiddling, %w( 17 | enhex unhex urlencode urldecode bits bits_str unbits bitswap b64e b64d xor 18 | ) 19 | end 20 | end 21 | end 22 | end 23 | 24 | ::String.include ::Pwnlib::Ext::String::InstanceMethods 25 | -------------------------------------------------------------------------------- /lib/pwnlib/memleak.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/util/packing' 5 | 6 | module Pwnlib 7 | # A class caching and heuristic tool for exploiting memory leaks. 8 | class MemLeak 9 | # Instantiate a {Pwnlib::MemLeak} object. 10 | # 11 | # @yieldparam [Integer] leak_addr 12 | # The start address that the leaker should leak from. 13 | # 14 | # @yieldreturn [String] 15 | # A leaked non-empty byte string, starting from +leak_addr+. 16 | def initialize(&block) 17 | @leak = block 18 | @cache = {} 19 | end 20 | 21 | # Leak +numb+ bytes at +addr+. 22 | # Returns a string with the leaked bytes. 23 | # 24 | # @param [Integer] addr 25 | # The starting address of the leak. 26 | # @param [Integer] numb 27 | # Number of bytes to be leaked. 28 | # 29 | # @return [String] 30 | # The leaked byte string. 31 | def n(addr, numb) 32 | (0...numb).map { |i| do_leak(addr + i) }.pack('C*') 33 | end 34 | 35 | # Leak a byte at +*((uint8_t*) addr)+. 36 | # 37 | # @param [Integer] addr 38 | # The address of the leak. 39 | # 40 | # @return [Integer] 41 | # The leaked byte. 42 | def b(addr) 43 | Util::Packing.u8(n(addr, 1)) 44 | end 45 | 46 | # Leak a word at +*((uint16_t*) addr)+. 47 | # 48 | # @param [Integer] addr 49 | # The address of the leak. 50 | # 51 | # @return [Integer] 52 | # The leaked word. 53 | def w(addr) 54 | Util::Packing.u16(n(addr, 2)) 55 | end 56 | 57 | # Leak a dword at +*((uint32_t*) addr)+. 58 | # 59 | # @param [Integer] addr 60 | # The address of the leak. 61 | # 62 | # @return [Integer] 63 | # The leaked dword. 64 | def d(addr) 65 | Util::Packing.u32(n(addr, 4)) 66 | end 67 | 68 | # Leak a qword at +*((uint64_t*) addr)+. 69 | # 70 | # @param [Integer] addr 71 | # The address of the leak. 72 | # 73 | # @return [Integer] 74 | # The leaked qword. 75 | def q(addr) 76 | Util::Packing.u64(n(addr, 8)) 77 | end 78 | 79 | private 80 | 81 | # Call the leaker function on address +addr+. 82 | # The result would be cached. 83 | def do_leak(addr) 84 | unless @cache.key?(addr) 85 | data = @leak.call(addr) 86 | data.bytes.each.with_index(addr) { |b, i| @cache[i] = b } 87 | end 88 | @cache[addr] 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/pwnlib/pwn.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | # require this file would also require all things in pwnlib, but would not pollute anything. 5 | 6 | require 'pwnlib/asm' 7 | require 'pwnlib/constants/constant' 8 | require 'pwnlib/constants/constants' 9 | require 'pwnlib/context' 10 | require 'pwnlib/dynelf' 11 | require 'pwnlib/elf/elf' 12 | require 'pwnlib/errors' 13 | require 'pwnlib/logger' 14 | require 'pwnlib/reg_sort' 15 | require 'pwnlib/runner' 16 | require 'pwnlib/shellcraft/shellcraft' 17 | require 'pwnlib/tubes/process' 18 | require 'pwnlib/tubes/serialtube' 19 | require 'pwnlib/tubes/sock' 20 | require 'pwnlib/ui' 21 | require 'pwnlib/util/cyclic' 22 | require 'pwnlib/util/fiddling' 23 | require 'pwnlib/util/getdents' 24 | require 'pwnlib/util/hexdump' 25 | require 'pwnlib/util/lists' 26 | require 'pwnlib/util/packing' 27 | 28 | # include this module in a class to use all pwnlib functions in that class instance. 29 | module Pwn 30 | include ::Pwnlib::Asm 31 | include ::Pwnlib::Context 32 | include ::Pwnlib::Logger 33 | include ::Pwnlib::Runner 34 | include ::Pwnlib::UI 35 | include ::Pwnlib::Util::Cyclic 36 | include ::Pwnlib::Util::Fiddling 37 | include ::Pwnlib::Util::HexDump 38 | include ::Pwnlib::Util::Lists 39 | include ::Pwnlib::Util::Packing 40 | 41 | def shellcraft 42 | ::Pwnlib::Shellcraft::Shellcraft.instance 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/pwnlib/runner.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'fileutils' 5 | 6 | require 'pwnlib/asm' 7 | require 'pwnlib/tubes/process' 8 | 9 | module Pwnlib 10 | # This module collects the methods for executing codes, e.g., assembly code, assembled machine code, etc. 11 | module Runner 12 | module_function 13 | 14 | # Given an assembly listing, assemble and execute it. 15 | # 16 | # @param [String] assembly 17 | # Assembly code. 18 | # 19 | # @return [Pwnlib::Tubes::Process] 20 | # The tube for interacting. 21 | # 22 | # @see Runner.run_shellcode 23 | def run_assembly(assembly) 24 | run_shellcode(::Pwnlib::Asm.asm(assembly)) 25 | end 26 | 27 | # Given assembled machine code bytes, execute them. 28 | # 29 | # @param [String] bytes 30 | # Assembled code. 31 | # 32 | # @return [Pwnlib::Tubes::Process] 33 | # The tube for interacting. 34 | # 35 | # @example 36 | # r = run_shellcode(asm(shellcraft.cat('/etc/passwd'))) 37 | # r.interact 38 | # # [INFO] Switching to interactive mode 39 | # # root:x:0:0:root:/root:/bin/bash 40 | # # daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 41 | # # bin:x:2:2:bin:/bin:/usr/sbin/nologin 42 | # # sys:x:3:3:sys:/dev:/usr/sbin/nologin 43 | # # sync:x:4:65534:sync:/bin:/bin/sync 44 | # # games:x:5:60:games:/usr/games:/usr/sbin/nologin 45 | # # [INFO] Got EOF in interactive mode 46 | # #=> true 47 | def run_shellcode(bytes) 48 | file = ::Pwnlib::Asm.make_elf(bytes, to_file: true) 49 | at_exit { FileUtils.rm_f(file) if File.exist?(file) } 50 | ::Pwnlib::Tubes::Process.new(file) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/helper' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module Amd64 9 | # For non os-related methods. 10 | module Common 11 | extend ::Pwnlib::Shellcraft::Generators::Helper 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/infloop' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Common 12 | # @overload infloop 13 | # 14 | # @see Generators::X86::Common#infloop 15 | def infloop(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Common.infloop(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | require 'pwnlib/shellcraft/generators/amd64/common/setregs' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Common 12 | # Like +memcpy+ in glibc. 13 | # 14 | # Copy +n+ bytes from +src+ to +dst+. 15 | # 16 | # @param [String, Symbol, Integer] dst 17 | # Destination. 18 | # @param [String, Symbol, Integer] src 19 | # Source to be copied. 20 | # @param [Integer] n 21 | # The number of bytes to be copied. 22 | # 23 | # @example 24 | # shellcraft.memcpy('rax', 'rbx', 0x1000) 25 | def memcpy(dst, src, n) 26 | cat "/* memcpy(#{pretty(dst)}, #{pretty(src)}, #{pretty(n)}) */" 27 | cat 'cld' 28 | cat Common.setregs({ rdi: dst, rsi: src, rcx: n }) 29 | cat 'rep movsb' 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/nop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/amd64/common/common' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module Amd64 9 | module Common 10 | # A no-op instruction. 11 | def nop 12 | cat 'nop' 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/popad.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module Amd64 10 | module Common 11 | # Pop all of the registers onto the stack which i386 +popad+ does. 12 | def popad 13 | cat <<-EOS 14 | pop rdi 15 | pop rsi 16 | pop rbp 17 | pop rbx /* add rsp, 8 */ 18 | pop rbx 19 | pop rdx 20 | pop rcx 21 | pop rax 22 | EOS 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module Amd64 10 | module Common 11 | # Push a string to stack. 12 | # 13 | # @param [String] str 14 | # String to be pushed. 15 | # @param [Boolean] append_null 16 | # If need to append a null byte in the end of +str+. 17 | # 18 | # @example 19 | # context.arch = 'amd64' 20 | # puts shellcraft.pushstr('pusheen') 21 | # # /* push "pusheen\x00" */ 22 | # # mov rax, 0x101010101010101 23 | # # push rax 24 | # # mov rax, 0x101010101010101 ^ 0x6e656568737570 25 | # # xor [rsp], rax 26 | # #=> nil 27 | def pushstr(str, append_null: true) 28 | # This will not affect callee's +str+. 29 | str += "\x00" if append_null && !str.end_with?("\x00") 30 | return if str.empty? 31 | 32 | padding = str[-1].ord >= 128 ? "\xff" : "\x00" 33 | cat "/* push #{str.inspect} */" 34 | group(8, str, underfull_action: :fill, fill_value: padding).reverse_each do |word| 35 | sign = u64(word, endian: 'little', signed: true) 36 | sign32 = u32(word[0, 4], bits: 32, endian: 'little', signed: true) 37 | if [0, 0xa].include?(sign) # simple forbidden byte case 38 | cat "push #{pretty(sign + 1)}" 39 | cat 'dec byte ptr [rsp]' 40 | elsif sign >= -0x80 && sign <= 0x7f && okay(word[0]) # simple byte case 41 | cat "push #{pretty(sign)}" 42 | elsif sign >= -0x80000000 && sign <= 0x7fffffff && okay(word[0, 4]) 43 | # simple 32bit without forbidden byte 44 | cat "push #{pretty(sign)}" 45 | elsif okay(word) 46 | cat "mov rax, #{pretty(sign)}" 47 | cat 'push rax' 48 | elsif sign32.positive? && word[4, 4] == "\x00" * 4 49 | # The high 4 byte of word are all zeros, so we can use +xor dword ptr [rsp]+. 50 | a = u32(xor_pair(word[0, 4]).first, endian: 'little', signed: true) 51 | cat "push #{pretty(a)} ^ #{pretty(sign)}" 52 | cat "xor dword ptr [rsp], #{pretty(a)}" 53 | else 54 | a = u64(xor_pair(word).first, endian: 'little', signed: false) 55 | cat "mov rax, #{pretty(a)}" 56 | cat 'push rax' 57 | cat "mov rax, #{pretty(a ^ sign)} /* #{pretty(a)} ^ #{pretty(sign)} */" 58 | cat 'xor [rsp], rax' 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/pushstr_array' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Common 12 | # @overload pushstr_array(reg, array) 13 | # 14 | # @see Generators::X86::Common#pushstr_array 15 | def pushstr_array(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Common.pushstr_array(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/ret.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | require 'pwnlib/shellcraft/generators/amd64/common/mov' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Common 12 | # Instruction return. 13 | # 14 | # @param [String, Symbol, Integer] return_value 15 | # Set the return value. 16 | # Can be name of a register or an immediate value. 17 | # +nil+ for not set return value. 18 | # 19 | # @example 20 | # context.arch = 'amd64' 21 | # shellcraft.ret 22 | # #=> " ret" 23 | # shellcraft.ret(:rdi) 24 | # #=> " mov rax, rdi\n ret\n" 25 | def ret(return_value = nil) 26 | cat Common.mov('rax', return_value) if return_value 27 | cat 'ret' 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/setregs' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Common 12 | # @overload setregs(reg_context, stack_allowed: true) 13 | # 14 | # @see Generators::X86::Common#setregs 15 | def setregs(*args, **kwargs) 16 | context.local(arch: :amd64) do 17 | cat X86::Common.setregs(*args, **kwargs) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/cat' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload cat(filename, fd: 1) 13 | # 14 | # @see Generators::X86::Linux#cat 15 | def cat(*args, **kwargs) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.cat(*args, **kwargs) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/execve' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload execve(path, argv, envp) 13 | # 14 | # @see Generators::X86::Linux#execve 15 | def execve(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.execve(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/exit' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload exit(status = 0) 13 | # 14 | # @see Generators::X86::Linux#exit 15 | def exit(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.exit(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/helper' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module Amd64 9 | # For os-related methods. 10 | module Linux 11 | extend ::Pwnlib::Shellcraft::Generators::Helper 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/ls' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload ls(dir = '.') 13 | # 14 | # @see Generators::X86::Linux#ls 15 | def ls(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.ls(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/open.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/open' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload open(filename, flags = 'O_RDONLY', mode = 0) 13 | # 14 | # @see Generators::X86::Linux#open 15 | def open(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.open(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/sh' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload sh(argv: false) 13 | # 14 | # @see Generators::X86::Linux#sh 15 | def sh(**kwargs) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.sh(**kwargs) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/sleep.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/sleep' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload sleep(seconds) 13 | # 14 | # @see Generators::X86::Linux#sleep 15 | def sleep(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.sleep(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/amd64/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/syscall' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module Amd64 11 | module Linux 12 | # @overload syscall(*arguments) 13 | # 14 | # @see Generators::X86::Linux#syscall 15 | def syscall(*args) 16 | context.local(arch: :amd64) do 17 | cat X86::Linux.syscall(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/abi' 4 | require 'pwnlib/constants/constants' 5 | require 'pwnlib/context' 6 | require 'pwnlib/reg_sort' 7 | require 'pwnlib/shellcraft/registers' 8 | require 'pwnlib/util/fiddling' 9 | require 'pwnlib/util/lists' 10 | require 'pwnlib/util/packing' 11 | 12 | module Pwnlib 13 | module Shellcraft 14 | module Generators 15 | # Define methods for generator modules. 16 | # 17 | # This module must and can only be extended by Common or Linux module under {Generators}. 18 | module Helper 19 | # Provide a 'sandbox' for generators. 20 | class Runner 21 | class << self 22 | def label_num 23 | @label_num ||= 0 24 | @label_num += 1 25 | end 26 | end 27 | 28 | def clear 29 | @_output = StringIO.new 30 | end 31 | 32 | # Indent each line 2 spaces. 33 | def typesetting 34 | indent = @_output.string.lines.map do |line| 35 | next "#{line.strip}\n" if label_str?(line.strip) 36 | 37 | line == "\n" ? line : ' ' * 2 + line.lstrip 38 | end 39 | indent.join 40 | end 41 | 42 | private 43 | 44 | def cat(str) 45 | @_output.puts(str) 46 | end 47 | 48 | # @example 49 | # get_label('infloop') #=> 'infloop_1' 50 | def get_label(str) 51 | "#{str}_#{self.class.label_num}" 52 | end 53 | 54 | def okay(s, *a, **kw) 55 | s = pack(s, *a, **kw) if s.is_a?(Integer) 56 | !(s.include?("\x00") || s.include?("\n")) 57 | end 58 | 59 | def evaluate(item) 60 | return item if register?(item) 61 | 62 | Constants.eval(item) 63 | end 64 | 65 | # @param [Constants::Constant, String, Integer] n 66 | def pretty(n) 67 | case n 68 | when Constants::Constant 69 | format('%s /* %s */', pretty(n.to_i), n) 70 | when Integer 71 | n.abs < 10 ? n.to_s : hex(n) 72 | else 73 | n.inspect 74 | end 75 | end 76 | 77 | def label_str?(str) 78 | str.match(/\A\w+_\d+:\Z/) != nil 79 | end 80 | 81 | include ::Pwnlib::Context 82 | include ::Pwnlib::RegSort 83 | include ::Pwnlib::Shellcraft::Registers 84 | include ::Pwnlib::Util::Fiddling 85 | include ::Pwnlib::Util::Lists 86 | include ::Pwnlib::Util::Packing 87 | end 88 | 89 | class << self 90 | # Define a corresponding singleton method whenever an instance method is defined. 91 | def extended(mod) 92 | class << mod 93 | define_method(:method_added) do |m| 94 | # Define singleton methods, so we can invoke +Generators::X86::Common.pushstr_array+. 95 | # Each method runs in an independent 'runner', so methods would not effect each other. 96 | runner = Runner.new 97 | method = instance_method(m).bind(runner) 98 | define_singleton_method(m) do |*args, **kwargs| 99 | runner.clear 100 | # TODO(david942j): remove the check when we drop Ruby 2.6 support 101 | if kwargs.empty? 102 | method.call(*args) 103 | else 104 | method.call(*args, **kwargs) 105 | end 106 | runner.typesetting 107 | end 108 | end 109 | end 110 | end 111 | end 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/helper' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module I386 9 | # For non os-related methods. 10 | module Common 11 | extend ::Pwnlib::Shellcraft::Generators::Helper 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/infloop.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/infloop' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Common 12 | # @overload infloop 13 | # 14 | # @see Generators::X86::Common#infloop 15 | def infloop(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Common.infloop(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/memcpy.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/common/common' 5 | require 'pwnlib/shellcraft/generators/i386/common/setregs' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Common 12 | # Like +memcpy+ in glibc. 13 | # 14 | # Copy +n+ bytes from +src+ to +dst+. 15 | # 16 | # @param [String, Symbol, Integer] dst 17 | # Destination. 18 | # @param [String, Symbol, Integer] src 19 | # Source to be copied. 20 | # @param [Integer] n 21 | # The number of bytes to be copied. 22 | # 23 | # @see Amd64::Common#memcpy 24 | def memcpy(dst, src, n) 25 | cat "/* memcpy(#{pretty(dst)}, #{pretty(src)}, #{pretty(n)}) */" 26 | cat 'cld' 27 | cat Common.setregs({ edi: dst, esi: src, ecx: n }) 28 | cat 'rep movsb' 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/mov.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/common/common' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module I386 10 | module Common 11 | # Move +src+ into +dst+ without newlines and null bytes. 12 | # 13 | # See {Amd64::Common#mov} for parameters' details. 14 | def mov(dst, src, stack_allowed: true) 15 | raise ArgumentError, "#{dst} is not a register" unless register?(dst) 16 | 17 | dst = get_register(dst) 18 | raise ArgumentError, "cannot use #{dst} on i386" if dst.size > 32 || dst.is64bit 19 | 20 | if register?(src) 21 | src = get_register(src) 22 | raise ArgumentError, "cannot use #{src} on i386" if src.size > 32 || src.is64bit 23 | if dst.size < src.size && !dst.bigger.include?(src.name) 24 | raise ArgumentError, "cannot mov #{dst}, #{src}: dst is smaller than src" 25 | end 26 | else 27 | context.local(arch: 'i386') { src = evaluate(src) } 28 | raise ArgumentError, format('cannot mov %s, %d: dst is smaller than src', dst, src) unless dst.fits(src) 29 | 30 | # Calculate the packed version 31 | srcp = pack(src & ((1 << dst.size) - 1), bits: dst.size) 32 | 33 | # Calculate the unsigned and signed versions 34 | srcu = unpack(srcp, bits: dst.size, signed: false) 35 | srcs = unpack(srcp, bits: dst.size, signed: true) 36 | srcp_neg = p32(-src) 37 | srcp_not = p32(src ^ 0xffffffff) 38 | end 39 | if register?(src) 40 | if src == dst || dst.bigger.include?(src.name) 41 | cat "/* moving #{src} into #{dst}, but this is a no-op */" 42 | elsif dst.size > src.size 43 | cat "movzx #{dst}, #{src}" 44 | else 45 | cat "mov #{dst}, #{src}" 46 | end 47 | elsif src.is_a?(Numeric) # Constant or immi 48 | xor = ->(reg) { "xor #{reg.xor}, #{reg.xor}" } 49 | if src.zero? # special case for zeroes 50 | cat "xor #{dst}, #{dst} /* #{src} */" 51 | elsif stack_allowed && dst.size == 32 && src == 10 52 | cat "push 9 /* mov #{dst}, '\\n' */" 53 | cat "pop #{dst}" 54 | cat "inc #{dst}" 55 | elsif stack_allowed && dst.size == 32 && (-2**7 <= srcs && srcs < 2**7) && okay(srcp[0]) 56 | cat "push #{pretty(src)}" 57 | cat "pop #{dst}" 58 | elsif okay(srcp) 59 | # Easy case. This implies that the register size and value are the same. 60 | cat "mov #{dst}, #{pretty(src)}" 61 | elsif srcu < 2**8 && okay(srcp[0]) && dst.sizes.include?(8) 62 | # Move 8-bit value into reg. 63 | cat xor[dst] 64 | cat "mov #{dst.sizes[8]}, #{pretty(src)}" 65 | elsif srcu == srcu & 0xff00 && okay(srcp[1]) && dst.ff00 66 | # Target value is a 16-bit value with no data in the low 8 bits, we can use the 'AH' style register. 67 | cat xor[dst] 68 | cat "mov #{dst.ff00}, #{pretty(src)} >> 8" 69 | elsif srcu < 2**16 && okay(srcp[0, 2]) 70 | # Target value is a 16-bit value, use a 16-bit mov. 71 | cat xor[dst] 72 | cat "mov #{dst.sizes[16]}, #{pretty(src)}" 73 | elsif okay(srcp_neg) 74 | cat "mov #{dst}, -#{pretty(src)}" 75 | cat "neg #{dst}" 76 | elsif okay(srcp_not) 77 | cat "mov #{dst}, (-1) ^ #{pretty(src)}" 78 | cat "not #{dst}" 79 | else 80 | # All else has failed. Use some XOR magic to move things around. 81 | a, b = xor_pair(srcp, avoid: "\x00\n") 82 | a = hex(unpack(a, bits: dst.size)) 83 | b = hex(unpack(b, bits: dst.size)) 84 | cat "mov #{dst}, #{a}" 85 | cat "xor #{dst}, #{b} /* #{hex(src)} == #{a} ^ #{b} */" 86 | end 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/nop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/i386/common/common' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module I386 9 | module Common 10 | # A no-op instruction. 11 | def nop 12 | cat 'nop' 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/common/common' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module I386 10 | module Common 11 | # Push a string to stack. 12 | # 13 | # See {Amd64::Common#pushstr} for parameters' details. 14 | def pushstr(str, append_null: true) 15 | # This will not affect callee's +str+. 16 | str += "\x00" if append_null && !str.end_with?("\x00") 17 | return if str.empty? 18 | 19 | padding = str[-1].ord >= 128 ? "\xff" : "\x00" 20 | cat "/* push #{str.inspect} */" 21 | group(4, str, underfull_action: :fill, fill_value: padding).reverse_each do |word| 22 | sign = u32(word, endian: 'little', signed: true) 23 | if [0, 0xa].include?(sign) # simple forbidden byte case 24 | cat "push #{pretty(sign + 1)}" 25 | cat 'dec byte ptr [esp]' 26 | elsif sign >= -128 && sign <= 127 27 | cat "push #{pretty(sign)}" 28 | elsif okay(word) 29 | cat "push #{pretty(sign)}" 30 | else 31 | a = u32(xor_pair(word).first, endian: 'little', signed: false) 32 | cat "push #{pretty(a)}" 33 | cat "xor dword ptr [esp], #{pretty(a ^ sign)} /* #{pretty(a)} ^ #{pretty(sign)} */" 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/pushstr_array' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Common 12 | # @overload pushstr_array(reg, array) 13 | # 14 | # @see Generators::X86::Common#pushstr_array 15 | def pushstr_array(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Common.pushstr_array(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/common/setregs.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/setregs' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Common 12 | # @overload setregs(reg_context, stack_allowed: true) 13 | # 14 | # @see Generators::X86::Common#setregs 15 | def setregs(*args, **kwargs) 16 | context.local(arch: :i386) do 17 | cat X86::Common.setregs(*args, **kwargs) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/cat.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/cat' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload cat(filename, fd: 1) 13 | # 14 | # @see Generators::X86::Linux#cat 15 | def cat(*args, **kwargs) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.cat(*args, **kwargs) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/execve.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/execve' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload execve(path, argv, envp) 13 | # 14 | # @see Generators::X86::Linux#execve 15 | def execve(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.execve(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/exit.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/exit' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload exit(status = 0) 13 | # 14 | # @see Generators::X86::Linux#exit 15 | def exit(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.exit(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/linux.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/helper' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module I386 9 | # For os-related methods. 10 | module Linux 11 | extend ::Pwnlib::Shellcraft::Generators::Helper 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/ls.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/ls' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload ls(dir = '.') 13 | # 14 | # @see Generators::X86::Linux#ls 15 | def ls(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.ls(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/open.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/open' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload open(filename, flags = 'O_RDONLY', mode = 0) 13 | # 14 | # @see Generators::X86::Linux#open 15 | def open(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.open(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/sh.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/sh' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload sh(argv: false) 13 | # 14 | # @see Generators::X86::Linux#sh 15 | def sh(**kwargs) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.sh(**kwargs) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/sleep.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/sleep' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload sleep(seconds) 13 | # 14 | # @see Generators::X86::Linux#sleep 15 | def sleep(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.sleep(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/i386/linux/linux' 5 | require 'pwnlib/shellcraft/generators/x86/linux/syscall' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module I386 11 | module Linux 12 | # @overload syscall(*arguments) 13 | # 14 | # @see Generators::X86::Linux#syscall 15 | def syscall(*args) 16 | context.local(arch: :i386) do 17 | cat X86::Linux.syscall(*args) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/helper' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module X86 9 | # For non os-related methods. 10 | module Common 11 | class << self 12 | def define_arch_dependent_method(method) 13 | define_method(method) do |*args, **kwargs| 14 | case context.arch 15 | when 'amd64' 16 | cat Amd64::Common.public_send(method, *args, **kwargs) 17 | when 'i386' 18 | cat I386::Common.public_send(method, *args, **kwargs) 19 | end 20 | end 21 | end 22 | end 23 | 24 | extend ::Pwnlib::Shellcraft::Generators::Helper 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/infloop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/x86/common/common' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module X86 9 | module Common 10 | # Infinite loop. 11 | # 12 | # @example 13 | # shellcraft.infloop 14 | # #=> "infloop_1:\n jmp infloop_1" 15 | def infloop 16 | label = get_label('infloop') 17 | cat "#{label}:" 18 | cat "jmp #{label}" 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/memcpy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/amd64/common/memcpy' 4 | require 'pwnlib/shellcraft/generators/i386/common/memcpy' 5 | require 'pwnlib/shellcraft/generators/x86/common/common' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module X86 11 | module Common 12 | define_arch_dependent_method :memcpy 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/mov.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/amd64/common/mov' 4 | require 'pwnlib/shellcraft/generators/i386/common/mov' 5 | require 'pwnlib/shellcraft/generators/x86/common/common' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module X86 11 | module Common 12 | define_arch_dependent_method :mov 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/amd64/common/pushstr' 4 | require 'pwnlib/shellcraft/generators/i386/common/pushstr' 5 | require 'pwnlib/shellcraft/generators/x86/common/common' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module X86 11 | module Common 12 | define_arch_dependent_method :pushstr 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/common' 5 | require 'pwnlib/shellcraft/generators/x86/common/mov' 6 | require 'pwnlib/shellcraft/generators/x86/common/pushstr' 7 | 8 | module Pwnlib 9 | module Shellcraft 10 | module Generators 11 | module X86 12 | module Common 13 | # Push an array of pointers onto the stack. 14 | # 15 | # @param [String] reg 16 | # Destination register to hold the result pointer. 17 | # @param [Array] array 18 | # List of arguments to push. 19 | # NULL termination is normalized so that each argument ends with exactly one NULL byte. 20 | # 21 | # @example 22 | # context.arch = 'i386' 23 | # puts shellcraft.pushstr_array('eax', ['push', 'een']) 24 | # # /* push argument array ["push\x00", "een\x00"] */ 25 | # # /* push "push\x00een\x00" */ 26 | # # push 1 27 | # # dec byte ptr [esp] 28 | # # push 0x1010101 29 | # # xor dword ptr [esp], 0x1010101 ^ 0x6e656500 30 | # # push 0x68737570 31 | # # xor eax, eax /* 0 */ 32 | # # push eax /* null terminate */ 33 | # # push 9 34 | # # pop eax 35 | # # add eax, esp 36 | # # push eax /* "een\x00" */ 37 | # # push 8 38 | # # pop eax 39 | # # add eax, esp 40 | # # push eax /* "push\x00" */ 41 | # # mov eax, esp 42 | # #=> nil 43 | # @example 44 | # context.arch = 'amd64' 45 | # puts shellcraft.pushstr_array('rax', ['meow', 'oh']) 46 | # # /* push argument array ["meow\x00", "oh\x00"] */ 47 | # # /* push "meow\x00oh\x00" */ 48 | # # mov rax, 0x101010101010101 49 | # # push rax 50 | # # mov rax, 0x101010101010101 ^ 0x686f00776f656d 51 | # # xor [rsp], rax 52 | # # xor eax, eax /* 0 */ 53 | # # push rax /* null terminate */ 54 | # # push 0xd 55 | # # pop rax 56 | # # add rax, rsp 57 | # # push rax /* "oh\x00" */ 58 | # # push 0x10 59 | # # pop rax 60 | # # add rax, rsp 61 | # # push rax /* "meow\x00" */ 62 | # # mov rax, rsp 63 | # #=> nil 64 | def pushstr_array(reg, array) 65 | abi = ::Pwnlib::ABI::ABI.default 66 | array = array.map { |a| "#{a.gsub(/\x00+\Z/, '')}\x00" } 67 | array_str = array.join 68 | word_size = abi.arg_alignment 69 | offset = array_str.size + word_size 70 | cat "/* push argument array #{array.inspect} */" 71 | cat Common.pushstr(array_str) 72 | cat Common.mov(reg, 0) 73 | cat "push #{reg} /* null terminate */" 74 | array.reverse.each_with_index do |arg, i| 75 | cat Common.mov(reg, offset + word_size * i - arg.size) 76 | cat "add #{reg}, #{abi.stack_pointer}" 77 | cat "push #{reg} /* #{arg.inspect} */" 78 | offset -= arg.size 79 | end 80 | cat Common.mov(reg, abi.stack_pointer) 81 | end 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/common/setregs.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/common' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module X86 10 | module Common 11 | # Set registers to given values. See example for clearly usage. 12 | # 13 | # @param [Hash{Symbol => String, Symbol, Numeric}] reg_context 14 | # The values of each registers to be set, see examples. 15 | # @param [Boolean] stack_allowed 16 | # If we can use stack for setting values. 17 | # With +stack_allowed+ equals +true+, shellcode would be shorter. 18 | # 19 | # @example 20 | # context.arch = 'i386' 21 | # puts shellcraft.setregs({ eax: 'ebx', ebx: 'ecx', ecx: 0x123 }) 22 | # # mov eax, ebx 23 | # # mov ebx, ecx 24 | # # xor ecx, ecx 25 | # # mov cx, 0x123 26 | # @example 27 | # context.arch = 'amd64' 28 | # puts shellcraft.setregs({ rdi: 'rsi', rsi: 'rdi' }) 29 | # # xchg rdi, rsi 30 | # 31 | # puts shellcraft.setregs({ rax: -1 }) 32 | # # push -1 33 | # # pop rax 34 | # 35 | # puts shellcraft.setregs({ rax: -1 }, stack_allowed: false) 36 | # # mov rax, -1 37 | def setregs(reg_context, stack_allowed: true) 38 | abi = ::Pwnlib::ABI::ABI.default 39 | reg_context = reg_context.reject { |_, v| v.nil? } 40 | # convert all registers to string 41 | reg_context = reg_context.map do |k, v| 42 | v = register?(v) ? v.to_s : v 43 | [k.to_s, v] 44 | end 45 | reg_context = reg_context.to_h 46 | ax_str, dx_str = abi.cdq_pair 47 | eax = reg_context[ax_str] 48 | edx = reg_context[dx_str] 49 | cdq = false 50 | ev = lambda do |reg| 51 | return reg unless reg.is_a?(String) 52 | 53 | evaluate(reg) 54 | end 55 | eax = ev[eax] 56 | edx = ev[edx] 57 | 58 | if eax.is_a?(Numeric) && edx.is_a?(Numeric) && edx.zero? && (eax & (1 << 31)).zero? 59 | # @diff 60 | # The condition is wrong in python-pwntools, and here we don't care the case of edx==0xffffffff. 61 | cdq = true 62 | reg_context.delete(dx_str) 63 | end 64 | sorted_regs = regsort(reg_context, registers) 65 | if sorted_regs.empty? 66 | cat '/* setregs noop */' 67 | else 68 | sorted_regs.each do |how, src, dst| 69 | if how == 'xchg' 70 | cat "xchg #{src}, #{dst}" 71 | else 72 | # Bug in python-pwntools, which is missing `stack_allowed`. 73 | # Proof of bug: pwnlib.shellcraft.setregs({'rax': 1}, stack_allowed=False) 74 | cat Common.mov(src, dst, stack_allowed: stack_allowed) 75 | end 76 | end 77 | end 78 | cat "cdq /* #{dx_str}=0 */" if cdq 79 | end 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/cat.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/pushstr' 5 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 6 | require 'pwnlib/shellcraft/generators/x86/linux/syscall' 7 | 8 | module Pwnlib 9 | module Shellcraft 10 | module Generators 11 | module X86 12 | module Linux 13 | # Opens a file and writes its contents to the specified file descriptor. 14 | # 15 | # @param [String] filename 16 | # The filename. 17 | # @param [Integer] fd 18 | # The file descriptor to write the file contents. 19 | # 20 | # @example 21 | # context.arch = 'amd64' 22 | # puts shellcraft.cat('/etc/passwd') 23 | # # /* push "/etc/passwd\x00" */ 24 | # # push 0x1010101 ^ 0x647773 25 | # # xor dword ptr [rsp], 0x1010101 26 | # # mov rax, 0x7361702f6374652f 27 | # # push rax 28 | # # /* call open("rsp", 0, "O_RDONLY") */ 29 | # # push 2 /* (SYS_open) */ 30 | # # pop rax 31 | # # mov rdi, rsp 32 | # # xor esi, esi /* 0 */ 33 | # # cdq /* rdx=0 */ 34 | # # syscall 35 | # # /* call sendfile(1, "rax", 0, 2147483647) */ 36 | # # push 1 37 | # # pop rdi 38 | # # mov rsi, rax 39 | # # push 0x28 /* (SYS_sendfile) */ 40 | # # pop rax 41 | # # mov r10d, 0x7fffffff 42 | # # cdq /* rdx=0 */ 43 | # # syscall 44 | # #=> nil 45 | def cat(filename, fd: 1) 46 | abi = ::Pwnlib::ABI::ABI.syscall 47 | cat Linux.open(filename, 'O_RDONLY') 48 | cat Linux.syscall('SYS_sendfile', fd, abi.register_arguments.first, 0, 0x7fffffff) 49 | end 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/execve.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/pushstr' 5 | require 'pwnlib/shellcraft/generators/x86/common/pushstr_array' 6 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 7 | 8 | module Pwnlib 9 | module Shellcraft 10 | module Generators 11 | module X86 12 | module Linux 13 | # Execute a different process. 14 | # 15 | # @param [String] path 16 | # Can be either an absolute path or a register's name. 17 | # @param [String, Array, Integer, nil] argv 18 | # If +argv+ is a +String+, it would be seen as a register. 19 | # If +Array+, works like normal arguments array. 20 | # If +Integer+, take it as a pointer adrress. (same as +nil+ if zero is given.) 21 | # If +nil+, use NULL pointer. 22 | # @param [String, Hash{#to_s => #to_s}, Integer, nil] envp 23 | # +String+ for register name. 24 | # If +envp+ is a +Hash+, it will be converted into the environ form (i.e. key=value). 25 | # If +Integer+, take it as a pointer address (same as +nil+ if zero is given). 26 | # If +nil+ is given, use NULL pointer. 27 | # 28 | # @example 29 | # shellcraft.execve('/bin/sh', ['sh'], {PWD: '.'}) 30 | # 31 | # @diff 32 | # Parameters have no default values since this is a basic function. 33 | def execve(path, argv, envp) 34 | abi = ::Pwnlib::ABI::ABI.syscall 35 | argv = case argv 36 | when String 37 | raise ArgumentError, "#{argv.inspect} is not a valid register name" unless register?(argv) 38 | 39 | argv 40 | when Array 41 | cat Common.pushstr_array(abi.register_arguments[2], argv) 42 | cat '' 43 | abi.register_arguments[2] 44 | when Integer, nil 45 | argv.to_i 46 | end 47 | 48 | envp = case envp 49 | when String 50 | raise ArgumentError, "#{envp.inspect} is not a valid register name" unless register?(envp) 51 | 52 | envp 53 | when Hash 54 | cat Common.pushstr_array(abi.register_arguments[3], envp.map { |k, v| "#{k}=#{v}" }) 55 | cat '' 56 | abi.register_arguments[3] 57 | when Integer, nil 58 | envp.to_i 59 | end 60 | 61 | unless register?(path) 62 | cat Common.pushstr(path) 63 | cat '' 64 | path = abi.stack_pointer 65 | end 66 | cat Linux.syscall('SYS_execve', path, argv, envp) 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/exit.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module X86 10 | module Linux 11 | # Exit syscall. 12 | # 13 | # @param [Integer] status 14 | # Status code. 15 | # 16 | # @return [String] 17 | # Assembly for invoking exit syscall. 18 | # 19 | # @example 20 | # puts shellcraft.exit(1) 21 | # # /* call exit(1) */ 22 | # # push 1 /* (SYS_exit) */ 23 | # # pop eax 24 | # # push 1 25 | # # pop ebx 26 | # # int 0x80 27 | def exit(status = 0) 28 | cat Linux.syscall('SYS_exit', status) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/linux.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pwnlib/shellcraft/generators/helper' 4 | 5 | module Pwnlib 6 | module Shellcraft 7 | module Generators 8 | module X86 9 | # For os-related methods. 10 | module Linux 11 | extend ::Pwnlib::Shellcraft::Generators::Helper 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/ls.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/pushstr' 5 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 6 | require 'pwnlib/shellcraft/generators/x86/linux/syscall' 7 | 8 | module Pwnlib 9 | module Shellcraft 10 | module Generators 11 | module X86 12 | module Linux 13 | # List files. 14 | # 15 | # @param [String] dir 16 | # The relative path to be listed. 17 | # 18 | # @example 19 | # context.arch = 'amd64' 20 | # puts shellcraft.ls 21 | # # /* push ".\x00" */ 22 | # # push 0x2e 23 | # # /* call open("rsp", 0, 0) */ 24 | # # push 2 /* (SYS_open) */ 25 | # # pop rax 26 | # # mov rdi, rsp 27 | # # xor esi, esi /* 0 */ 28 | # # cdq /* rdx=0 */ 29 | # # syscall 30 | # # /* call getdents("rax", "rsp", 4096) */ 31 | # # mov rdi, rax 32 | # # push 0x4e /* (SYS_getdents) */ 33 | # # pop rax 34 | # # mov rsi, rsp 35 | # # xor edx, edx 36 | # # mov dh, 0x1000 >> 8 37 | # # syscall 38 | # # /* call write(1, "rsp", "rax") */ 39 | # # push 1 40 | # # pop rdi 41 | # # mov rsi, rsp 42 | # # mov rdx, rax 43 | # # push 1 /* (SYS_write) */ 44 | # # pop rax 45 | # # syscall 46 | # #=> nil 47 | # 48 | # @note 49 | # This shellcode will output the binary data returned by syscall +getdents+. 50 | # Use {Pwnlib::Util::Getdents.parse} to parse the output. 51 | def ls(dir = '.') 52 | abi = ::Pwnlib::ABI::ABI.syscall 53 | cat Common.pushstr(dir) 54 | cat Linux.syscall('SYS_open', abi.stack_pointer, 0, 0) 55 | # In x86, return value register is same as sysnr register. 56 | ret = abi.register_arguments.first 57 | # XXX(david942j): Will fixed size 0x1000 be an issue? 58 | cat Linux.syscall('SYS_getdents', ret, abi.stack_pointer, 0x1000) # getdents(fd, buf, sz) 59 | 60 | # Just write all the shits out 61 | cat Linux.syscall('SYS_write', 1, abi.stack_pointer, ret) 62 | end 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/open.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 5 | 6 | module Pwnlib 7 | module Shellcraft 8 | module Generators 9 | module X86 10 | module Linux 11 | # Push filename onto stack and perform open syscall. 12 | # 13 | # @param [String] filename 14 | # The file to be opened. 15 | # @param [String, Integer] flags 16 | # Flags for opening a file. 17 | # @param [Integer] mode 18 | # If +filename+ doesn't exist and 'O_CREAT' is specified in +flags+, 19 | # +mode+ will be used as the file permission for creating the file. 20 | # 21 | # @return [String] 22 | # Assembly for syscall open. 23 | # 24 | # @example 25 | # puts shellcraft.open('/etc/passwd', 'O_RDONLY') 26 | # # /* push "/etc/passwd\x00" */ 27 | # # push 0x1010101 28 | # # xor dword ptr [esp], 0x1657672 /* 0x1010101 ^ 0x647773 */ 29 | # # push 0x7361702f 30 | # # push 0x6374652f 31 | # # /* call open("esp", "O_RDONLY", 0) */ 32 | # # push 5 /* (SYS_open) */ 33 | # # pop eax 34 | # # mov ebx, esp 35 | # # xor ecx, ecx /* (O_RDONLY) */ 36 | # # cdq /* edx=0 */ 37 | # # int 0x80 38 | def open(filename, flags = 'O_RDONLY', mode = 0) 39 | abi = ::Pwnlib::ABI::ABI.syscall 40 | cat Common.pushstr(filename) 41 | cat Linux.syscall('SYS_open', abi.stack_pointer, flags, mode) 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/sh.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/linux/execve' 5 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 6 | 7 | module Pwnlib 8 | module Shellcraft 9 | module Generators 10 | module X86 11 | module Linux 12 | # Get shell! 13 | # 14 | # @param [Boolean, Array] argv 15 | # Arguments of +argv+ when calling +execve+. 16 | # If +true+ is given, use +['sh']+. 17 | # If +Array+ is given, use it as arguments array. 18 | # 19 | # @example 20 | # context.arch = 'i386' 21 | # puts shellcraft.sh 22 | # # /* push "/bin///sh\x00" */ 23 | # # push 0x68 24 | # # push 0x732f2f2f 25 | # # push 0x6e69622f 26 | # # 27 | # # /* call execve("esp", 0, 0) */ 28 | # # push 0xb /* (SYS_execve) */ 29 | # # pop eax 30 | # # mov ebx, esp 31 | # # xor ecx, ecx /* 0 */ 32 | # # cdq /* edx=0 */ 33 | # # int 0x80 34 | # #=> nil 35 | # 36 | # @note Null pointer is always used as +envp+. 37 | # 38 | # @diff 39 | # By default, this method calls +execve('/bin///sh', 0, 0)+, which is different from pwntools-python: 40 | # +execve('/bin///sh', ['sh'], 0)+. 41 | def sh(argv: false) 42 | argv = case argv 43 | when true then ['sh'] 44 | when false then 0 45 | else argv 46 | end 47 | cat Linux.execve('/bin///sh', argv, 0) 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/sleep.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/pushstr' 5 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 6 | require 'pwnlib/shellcraft/generators/x86/linux/syscall' 7 | require 'pwnlib/util/packing' 8 | 9 | module Pwnlib 10 | module Shellcraft 11 | module Generators 12 | module X86 13 | module Linux 14 | # Sleep for a specified number of seconds. 15 | # 16 | # @param [Float] seconds 17 | # The seconds to sleep. 18 | # 19 | # @example 20 | # context.arch = :amd64 21 | # puts shellcraft.sleep(1) 22 | # # /* push "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" */ 23 | # # push 1 24 | # # dec byte ptr [rsp] 25 | # # push 1 26 | # # /* call nanosleep("rsp", 0) */ 27 | # # push 0x23 /* (SYS_nanosleep) */ 28 | # # pop rax 29 | # # mov rdi, rsp 30 | # # xor esi, esi /* 0 */ 31 | # # syscall 32 | # # add rsp, 16 /* recover rsp */ 33 | # #=> nil 34 | # 35 | # @note 36 | # Syscall +nanosleep+ accepts a data pointer as argument, the stack will be used for putting the data 37 | # needed. The generated assembly will use sizeof(struct timespec) = 16 bytes for putting data. 38 | def sleep(seconds) 39 | # pushes the data onto stack 40 | tv_sec = seconds.to_i 41 | tv_nsec = ((seconds - tv_sec) * 1e9).to_i 42 | data = ::Pwnlib::Util::Packing.p64(tv_sec) + ::Pwnlib::Util::Packing.p64(tv_nsec) 43 | cat Common.pushstr(data, append_null: false) 44 | sp = ::Pwnlib::ABI::ABI.default.stack_pointer 45 | cat Linux.syscall('SYS_nanosleep', sp, 0) 46 | cat "add #{sp}, #{data.size} /* recover #{sp} */" 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/shellcraft/generators/x86/common/setregs' 5 | require 'pwnlib/shellcraft/generators/x86/linux/linux' 6 | require 'pwnlib/util/ruby' 7 | 8 | module Pwnlib 9 | module Shellcraft 10 | module Generators 11 | module X86 12 | module Linux 13 | # Assembly of +syscall+. 14 | # 15 | # @example 16 | # context.arch = 'i386' 17 | # puts shellcraft.syscall('SYS_open', 'esp', 0, 0) 18 | # # /* call open("esp", 0, 0) */ 19 | # # push 5 /* (SYS_open) */ 20 | # # pop eax 21 | # # mov ebx, esp 22 | # # xor ecx, ecx /* 0 */ 23 | # # cdq /* edx=0 */ 24 | # # int 0x80 25 | # #=> nil 26 | def syscall(*arguments) 27 | abi = ::Pwnlib::ABI::ABI.syscall 28 | registers = abi.register_arguments 29 | reg_ctx = registers.zip(arguments).to_h 30 | syscall = arguments.first 31 | if syscall.to_s.start_with?('SYS_') 32 | fmt = "#{syscall.to_s[4..-1]}(%s)" 33 | args = [] 34 | else 35 | fmt = 'syscall(%s)' 36 | args = [syscall ? syscall.inspect : '?'] 37 | end 38 | # arg0 to arg5 39 | 1.upto(6) do |i| 40 | args.push(arguments[i] ? arguments[i].inspect : '?') 41 | end 42 | args.pop while args.last == '?' 43 | 44 | cat "/* call #{format(fmt, args.join(', '))} */" 45 | cat Common.setregs(reg_ctx) if arguments.any? { |v| !v.nil? } 46 | cat abi.syscall_str 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/pwnlib/shellcraft/shellcraft.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'singleton' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/errors' 8 | require 'pwnlib/logger' 9 | 10 | module Pwnlib 11 | # Implement shellcraft! 12 | # 13 | # All shellcode generators are defined under generators/*. 14 | # While typing +Shellcraft::Generators::I386::Linux.sh+ is too annoying, we define an instance +shellcraft+ in this 15 | # module, which let user invoke +shellcraft.sh+ directly. 16 | module Shellcraft 17 | # Singleton class. 18 | class Shellcraft 19 | include ::Singleton 20 | 21 | # All files under generators/ will be required. 22 | def initialize 23 | Dir[File.join(__dir__, 'generators', '**', '*.rb')].sort.each do |f| 24 | require f 25 | end 26 | end 27 | 28 | # Will search modules/methods under {Shellcraft::Generators} according to current arch and os. 29 | # i.e. +Shellcraft::Generators::${arch}::.${method}+. 30 | # 31 | # With this method, +context.local(arch: 'amd64') { shellcraft.sh }+ will invoke 32 | # {Shellcraft::Generators::Amd64::Linux#sh}. 33 | def method_missing(method, *args, **kwargs, &block) 34 | mod = find_module_for(method) 35 | return super if mod.nil? 36 | 37 | mod.public_send(method, *args, **kwargs, &block) 38 | end 39 | 40 | # For +respond_to?+. 41 | def respond_to_missing?(method, include_private = false) 42 | return true if find_module_for(method) 43 | 44 | super 45 | end 46 | 47 | private 48 | 49 | # @return [Module?] 50 | # +nil+ for not found. 51 | def find_module_for(method) 52 | begin 53 | arch_module = ::Pwnlib::Shellcraft::Generators.const_get(context.arch.capitalize) 54 | rescue NameError 55 | raise ::Pwnlib::Errors::UnsupportedArchError, 56 | "Can't use shellcraft under architecture #{context.arch.inspect}." 57 | end 58 | # try search in Common module 59 | common_module = arch_module.const_get(:Common) 60 | return common_module if common_module.singleton_methods.include?(method) 61 | 62 | # search in ${os} module 63 | os_module = arch_module.const_get(context.os.capitalize) 64 | return os_module if os_module.singleton_methods.include?(method) 65 | 66 | nil 67 | end 68 | 69 | include ::Pwnlib::Context 70 | include ::Pwnlib::Logger 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/pwnlib/timer.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'time' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/errors' 8 | 9 | module Pwnlib 10 | # A simple timer class. 11 | # TODO(Darkpi): Python pwntools seems to have many unreasonable codes in this class, 12 | # not sure of the use case of this, check if everything is coded as 13 | # intended after we have some use cases. (e.g. sock) 14 | # NOTE(Darkpi): This class is actually quite weird, and expected to be used only in tubes. 15 | class Timer 16 | # @diff We just use nil for default and :forever for forever. 17 | 18 | def initialize(timeout = nil) 19 | @deadline = nil 20 | @timeout = timeout 21 | end 22 | 23 | def started? 24 | @deadline 25 | end 26 | 27 | def active? 28 | started? && (@deadline == :forever || Time.now < @deadline) 29 | end 30 | 31 | def timeout 32 | return @timeout || ::Pwnlib::Context.context.timeout unless started? 33 | 34 | @deadline == :forever ? :forever : [@deadline - Time.now, 0].max 35 | end 36 | 37 | def timeout=(timeout) 38 | raise "Can't change timeout when countdown" if started? 39 | 40 | @timeout = timeout 41 | end 42 | 43 | # @diff We do NOT allow nested countdown with non-default value. This simplifies thing a lot. 44 | # NOTE(Darkpi): timeout = nil means default value for the first time, and nop after that. 45 | def countdown(timeout = nil) 46 | raise ArgumentError, 'Need a block for countdown' unless block_given? 47 | 48 | if started? 49 | return yield if timeout.nil? 50 | 51 | raise 'Nested countdown not permitted' 52 | end 53 | 54 | timeout ||= @timeout || ::Pwnlib::Context.context.timeout 55 | 56 | @deadline = timeout == :forever ? :forever : Time.now + timeout 57 | 58 | begin 59 | yield 60 | ensure 61 | was_active = active? 62 | @deadline = nil 63 | raise ::Pwnlib::Errors::TimeoutError unless was_active 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/pwnlib/tubes/buffer.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | module Pwnlib 5 | module Tubes 6 | # Buffer that support deque-like string operations. 7 | class Buffer 8 | # Instantiate a {Pwnlib::Tubes::Buffer} object. 9 | def initialize 10 | @data = [] 11 | @size = 0 12 | end 13 | 14 | attr_reader :size 15 | alias length size 16 | 17 | # Check whether the buffer is empty. 18 | # 19 | # @return [Boalean] 20 | # Returns true if contains no elements. 21 | def empty? 22 | size.zero? 23 | end 24 | 25 | # Python __contains__ and index is only correct with single-char input, which doesn't seems to 26 | # be useful, and they're not used anywhere either. Just ignore them for now. 27 | 28 | # Adds data to the buffer. 29 | # 30 | # @param [String] data 31 | # Data to add. 32 | def add(data) 33 | case data 34 | when Buffer 35 | @data.concat(data.data) 36 | else 37 | data = data.to_s.dup 38 | return if data.empty? 39 | 40 | @data << data 41 | end 42 | @size += data.size 43 | self 44 | end 45 | alias << add 46 | 47 | # Places data at the front of the buffer. 48 | # 49 | # @param [String] data 50 | # Data to place at the beginning of the buffer. 51 | def unget(data) 52 | case data 53 | when Buffer 54 | @data.unshift(*data.data) 55 | else 56 | data = data.to_s.dup 57 | return if data.empty? 58 | 59 | @data.unshift(data) 60 | end 61 | @size += data.size 62 | self 63 | end 64 | 65 | # Retrieves bytes from the buffer. 66 | # 67 | # @param [Integer?] n 68 | # Maximum number of bytes to fetch. 69 | # 70 | # @return [String] 71 | # Data as string. 72 | def get(n = nil) 73 | if n.nil? || n >= size 74 | data = @data.join 75 | @size = 0 76 | @data = [] 77 | else 78 | have = 0 79 | idx = 0 80 | while have < n 81 | have += @data[idx].size 82 | idx += 1 83 | end 84 | data = @data.slice!(0...idx).join 85 | if have > n 86 | extra = data.slice!(n..-1) 87 | @data.unshift(extra) 88 | end 89 | @size -= n 90 | end 91 | data 92 | end 93 | 94 | protected 95 | 96 | attr_reader :data 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/pwnlib/tubes/serialtube.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'rubyserial' 5 | 6 | require 'pwnlib/tubes/tube' 7 | 8 | module Pwnlib 9 | module Tubes 10 | # @!macro [new] raise_eof 11 | # @raise [Pwnlib::Errors::EndOfTubeError] 12 | # If the request is not satisfied when all data is received. 13 | 14 | # Serial Connections 15 | class SerialTube < Tube 16 | # Instantiate a {Pwnlib::Tubes::SerialTube} object. 17 | # 18 | # @param [String] port 19 | # A device name for rubyserial to open, e.g. /dev/ttypUSB0 20 | # @param [Integer] baudrate 21 | # Baud rate. 22 | # @param [Boolean] convert_newlines 23 | # If +true+, convert any +context.newline+s to +"\\r\\n"+ before 24 | # sending to remote. Has no effect on bytes received. 25 | # @param [Integer] bytesize 26 | # Serial character byte size. The '8' in '8N1'. 27 | # @param [Symbol] parity 28 | # Serial character parity. The 'N' in '8N1'. 29 | def initialize(port = nil, baudrate: 115_200, 30 | convert_newlines: true, 31 | bytesize: 8, parity: :none) 32 | super() 33 | 34 | # go hunting for a port 35 | port ||= Dir.glob('/dev/tty.usbserial*').first 36 | port ||= '/dev/ttyUSB0' 37 | 38 | @convert_newlines = convert_newlines 39 | @conn = Serial.new(port, baudrate, bytesize, parity) 40 | @serial_timer = Timer.new 41 | end 42 | 43 | # Closes the active connection 44 | def close 45 | @conn.close if @conn && !@conn.closed? 46 | @conn = nil 47 | end 48 | 49 | # Implementation of the methods required for tube 50 | private 51 | 52 | # Gets bytes over the serial connection until some bytes are received, or 53 | # +@timeout+ has passed. It is an error for it to return no data in less 54 | # than +@timeout+ seconds. It is ok for it to return some data in less 55 | # time. 56 | # 57 | # @param [Integer] numbytes 58 | # An upper limit on the number of bytes to get. 59 | # 60 | # @return [String] 61 | # A string containing read bytes. 62 | # 63 | # @!macro raise_eof 64 | def recv_raw(numbytes) 65 | raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil? 66 | 67 | @serial_timer.countdown do 68 | data = '' 69 | begin 70 | while @serial_timer.active? 71 | data += @conn.read(numbytes - data.length) 72 | break unless data.empty? 73 | 74 | sleep 0.1 75 | end 76 | # XXX(JonathanBeverley): should we reverse @convert_newlines here? 77 | return data 78 | rescue RubySerial::Error 79 | close 80 | raise ::Pwnlib::Errors::EndOfTubeError 81 | end 82 | end 83 | end 84 | 85 | # Sends bytes over the serial connection. This call will block until all the bytes are sent or an error occurs. 86 | # 87 | # @param [String] data 88 | # A string of the bytes to send. 89 | # 90 | # @return [Integer] 91 | # The number of bytes successfully written. 92 | # 93 | # @!macro raise_eof 94 | def send_raw(data) 95 | raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil? 96 | 97 | data.gsub!(context.newline, "\r\n") if @convert_newlines 98 | begin 99 | @conn.write(data) 100 | rescue RubySerial::Error 101 | close 102 | raise ::Pwnlib::Errors::EndOfTubeError 103 | end 104 | end 105 | 106 | # Sets the +timeout+ to use for subsequent +recv_raw+ calls. 107 | # 108 | # @param [Float] timeout 109 | def timeout_raw=(timeout) 110 | @serial_timer.timeout = timeout 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/pwnlib/tubes/sock.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'socket' 5 | 6 | require 'pwnlib/errors' 7 | require 'pwnlib/tubes/tube' 8 | 9 | module Pwnlib 10 | module Tubes 11 | # Socket! 12 | class Sock < Tube 13 | attr_reader :sock # @return [TCPSocket] The socket object. 14 | 15 | # Instantiate a {Pwnlib::Tubes::Sock} object. 16 | # 17 | # @param [String] host 18 | # The host to connect. 19 | # @param [Integer] port 20 | # The port to connect. 21 | # @param [Float?] timeout 22 | # See {Pwnlib::Tubes::Tube#initialize}. 23 | def initialize(host, port, timeout: nil) 24 | super(timeout: timeout) 25 | @sock = TCPSocket.new(host, port) 26 | @sock.binmode 27 | @timeout = nil 28 | @closed = { read: false, write: false } 29 | end 30 | 31 | # Close the TCPSocket if no arguments passed. 32 | # Or close the direction in +sock+. 33 | # 34 | # @param [:both, :recv, :read, :send, :write] direction 35 | # * Close the TCPSocket if +:both+ or no arguments passed. 36 | # * Disallow further read in +sock+ if +:recv+ or +:read+ passed. 37 | # * Disallow further write in +sock+ if +:send+ or +:write+ passed. 38 | # 39 | # @return [void] 40 | # 41 | # @diff In pwntools-python, method +shutdown(direction)+ is for closing socket one side, 42 | # +close()+ is for closing both side. We merge these two methods into one here. 43 | def close(direction = :both) 44 | if direction == :both 45 | return if @sock.closed? 46 | 47 | @closed[:read] = @closed[:write] = true 48 | @sock.close 49 | else 50 | shutdown(*normalize_direction(direction)) 51 | end 52 | end 53 | 54 | private 55 | 56 | alias io_out sock 57 | 58 | def shutdown(direction) 59 | return if @closed[direction] 60 | 61 | @closed[direction] = true 62 | 63 | case direction 64 | when :read 65 | @sock.close_read 66 | when :write 67 | @sock.close_write 68 | end 69 | end 70 | 71 | def timeout_raw=(timeout) 72 | @timeout = timeout 73 | end 74 | 75 | def send_raw(data) 76 | raise ::Pwnlib::Errors::EndOfTubeError if @closed[:write] 77 | 78 | begin 79 | @sock.write(data) 80 | rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED 81 | shutdown(:write) 82 | raise ::Pwnlib::Errors::EndOfTubeError 83 | end 84 | end 85 | 86 | def recv_raw(size) 87 | raise ::Pwnlib::Errors::EndOfTubeError if @closed[:read] 88 | 89 | begin 90 | rs, = IO.select([@sock], [], [], @timeout) 91 | return if rs.nil? 92 | 93 | @sock.readpartial(size) 94 | rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, EOFError 95 | shutdown(:read) 96 | raise ::Pwnlib::Errors::EndOfTubeError 97 | end 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/pwnlib/ui.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/logger' 5 | 6 | module Pwnlib 7 | # This module collects utilities that need user interactions. 8 | module UI 9 | module_function 10 | 11 | # Waits for user input. 12 | # 13 | # @return [void] 14 | def pause 15 | log.info('Paused (press enter to continue)') 16 | $stdin.gets 17 | end 18 | 19 | include ::Pwnlib::Logger 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pwnlib/util/cyclic.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | module Pwnlib 5 | module Util 6 | # Generate string with easy-to-find pattern. 7 | # 8 | # @example Call by specifying full module path. 9 | # require 'pwnlib/util/cyclic' 10 | # Pwnlib::Util::Cyclic.cyclic_find(Pwnlib::Util::Cyclic.cyclic(200)[123, 4]) #=> 123 11 | # @example require 'pwn' and have all methods. 12 | # require 'pwn' 13 | # cyclic_find(cyclic(200)[123, 4]) #=> 123 14 | module Cyclic 15 | # TODO(Darkpi): Should we put this constant in some 'String' module? 16 | ASCII_LOWERCASE = ('a'..'z').to_a.join 17 | private_constant :ASCII_LOWERCASE 18 | 19 | module_function 20 | 21 | # Generator for a sequence of unique substrings of length +n+. 22 | # This is implemented using a De Bruijn Sequence over the given +alphabet+. 23 | # Returns an Enumerator if no block given. 24 | # 25 | # @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4) 26 | # @param [String, Array] alphabet 27 | # Alphabet to be used. 28 | # @param [Integer] n 29 | # Length of substring that should be unique. 30 | # 31 | # @return [void] 32 | # 33 | # @yieldparam c 34 | # Item of the result sequence in order. 35 | # 36 | # @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4) 37 | # @param [String, Array] alphabet 38 | # Alphabet to be used. 39 | # @param [Integer] n 40 | # Length of substring that should be unique. 41 | # 42 | # @return [Enumerator] 43 | # The result sequence. 44 | def de_bruijn(alphabet: ASCII_LOWERCASE, n: 4) 45 | return to_enum(__method__, alphabet: alphabet, n: n) { alphabet.size**n } unless block_given? 46 | 47 | k = alphabet.size 48 | a = [0] * (k * n) 49 | 50 | db = lambda do |t, p| 51 | if t > n 52 | (1..p).each { |j| yield alphabet[a[j]] } if (n % p).zero? 53 | else 54 | a[t] = a[t - p] 55 | db.call(t + 1, p) 56 | (a[t - p] + 1...k).each do |j| 57 | a[t] = j 58 | db.call(t + 1, t) 59 | end 60 | end 61 | end 62 | 63 | db[1, 1] 64 | end 65 | 66 | # Simple wrapper over {.de_bruijn}, returning at most +length+ items. 67 | # 68 | # @param [Integer, nil] length 69 | # Desired length of the sequence, or +nil+ for the entire sequence. 70 | # @param [String, Array] alphabet 71 | # Alphabet to be used. 72 | # @param [Integer] n 73 | # Length of substring that should be unique. 74 | # 75 | # @return [String, Array] 76 | # The result sequence of at most +length+ items, with same type as +alphabet+. 77 | # 78 | # @example 79 | # cyclic(alphabet: 'ABC', n: 3) #=> 'AAABAACABBABCACBACCBBBCBCCC' 80 | # cyclic(20) #=> 'aaaabaaacaaadaaaeaaa' 81 | def cyclic(length = nil, alphabet: ASCII_LOWERCASE, n: 4) 82 | enum = de_bruijn(alphabet: alphabet, n: n) 83 | r = length.nil? ? enum.to_a : enum.take(length) 84 | alphabet.is_a?(String) ? r.join : r 85 | end 86 | 87 | # Find the position of a substring in a De Bruijn sequence. 88 | # 89 | # @param [String, Array] subseq 90 | # The substring to be found in the sequence. 91 | # @param [String, Array] alphabet 92 | # Alphabet to be used. 93 | # @param [Integer] n 94 | # Length of substring that should be unique. 95 | # Default to +subseq.size+. 96 | # 97 | # @return [Integer, nil] 98 | # The index +subseq+ first appear in the sequence, or +nil+ if not found. 99 | # 100 | # @todo Speed! See comment in Python pwntools. 101 | # 102 | # @example 103 | # cyclic_find(cyclic(300)[217, 4]) #=> 217 104 | def cyclic_find(subseq, alphabet: ASCII_LOWERCASE, n: nil) 105 | n ||= subseq.size 106 | subseq = subseq.chars if subseq.is_a?(String) 107 | return nil unless subseq.all? { |c| alphabet.include?(c) } 108 | 109 | pos = 0 110 | saved = [] 111 | de_bruijn(alphabet: alphabet, n: n).each do |c| 112 | saved << c 113 | if saved.size > subseq.size 114 | saved.shift 115 | pos += 1 116 | end 117 | return pos if saved == subseq 118 | end 119 | nil 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/pwnlib/util/getdents.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'bindata' 5 | 6 | require 'pwnlib/context' 7 | 8 | module Pwnlib 9 | module Util 10 | # Helper methods related to getdents syscall. 11 | module Getdents 12 | # For inverse mapping of +linux_dirent#d_type+. +man getdents+ to see more information. 13 | DT_TYPE_INVERSE = { 14 | 0 => 'UNKNOWN', 15 | 1 => 'FIFO', 16 | 2 => 'CHR', 17 | 4 => 'DIR', 18 | 6 => 'BLK', 19 | 8 => 'REG', 20 | 10 => 'LNK', 21 | 12 => 'SOCK' 22 | }.freeze 23 | 24 | # The +linux_dirent+ structure. 25 | class Dirent < ::BinData::Record 26 | attr_accessor :bits 27 | 28 | # struct linux_dirent { 29 | # unsigned long d_ino; /* Inode number */ 30 | # unsigned long d_off; /* Offset to next linux_dirent */ 31 | # unsigned short d_reclen; /* Length of this linux_dirent */ 32 | # char d_name[]; /* Filename (null-terminated) */ 33 | # /* length is actually (d_reclen - 2 - 34 | # offsetof(struct linux_dirent, d_name)) */ 35 | # /* 36 | # char pad; // Zero padding byte 37 | # char d_type; // File type (only since Linux 38 | # // 2.6.4); offset is (d_reclen - 1) 39 | # */ 40 | # } 41 | endian :big_and_little 42 | choice :d_ino, selection: :bits, choices: { 32 => :uint32, 64 => :uint64 } 43 | choice :d_off, selection: :bits, choices: { 32 => :uint32, 64 => :uint64 } 44 | uint16 :d_reclen 45 | string :d_name, read_length: -> { d_reclen - d_ino.num_bytes - d_off.num_bytes - 4 } 46 | int8 :pad 47 | int8 :d_type 48 | end 49 | 50 | module_function 51 | 52 | # Parse the output of getdents syscall. 53 | # For users to handle the shit-like output by +shellcraft.ls+ (e.g. {Shellcraft::Generators::X86::Linux#ls}). 54 | # 55 | # @param [String] binstr 56 | # The content returns by getdents syscall. 57 | # 58 | # @return [String] 59 | # Formatted output of filenames with file types. 60 | # 61 | # @example 62 | # context.arch = 'i386' 63 | # Util::Getdents.parse("\x92\x22\x0e\x01\x8f\x4a\xb3\x41" \ 64 | # "\x18\x00\x52\x45\x41\x44\x4d\x45" \ 65 | # "\x2e\x6d\x64\x00\x00\x00\x00\x08" \ 66 | # "\xb5\x10\x34\x01\xff\xff\xff\x7f" \ 67 | # "\x10\x00\x6c\x69\x62\x00\x00\x04") 68 | # #=> "REG README.md\nDIR lib\n" 69 | def parse(binstr) 70 | str = StringIO.new(binstr) 71 | result = StringIO.new 72 | until str.eof? 73 | ent = Dirent.new(endian: context.endian.to_sym) 74 | ent.bits = context.bits 75 | ent.read(str) 76 | # NOTE: d_name might contains garbage after first "\x00", so we use gsub(/\x00.*/) instead of delete("\x00"). 77 | result.puts("#{DT_TYPE_INVERSE[ent.d_type]} #{ent.d_name.gsub(/\x00.*/, '')}") 78 | end 79 | result.string 80 | end 81 | 82 | include ::Pwnlib::Context 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/pwnlib/util/lists.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'pwnlib/context' 5 | 6 | module Pwnlib 7 | module Util 8 | # Methods related to group / slice string into lists. 9 | module Lists 10 | module_function 11 | 12 | # Split sequence into subsequences of given size. If the values cannot be evenly distributed among into groups, 13 | # then the last group will either be dropped or padded with the value specified in +fill_value+. 14 | # 15 | # @param [Integer] n 16 | # The desired size of each subsequences. 17 | # @param [String] str 18 | # The sequence to be grouped. 19 | # @param [:ignore, :drop, :fill] underfull_action 20 | # Action to take when size of +str+ is not a mulitple of +n+. 21 | # @param [String] fill_value 22 | # The padding byte. 23 | # Only meaningful when +str+ cannot be grouped equally and +underfull_action == :fill+. 24 | # 25 | # @return [Array] 26 | # The split result. 27 | # 28 | # @example 29 | # slice(2, 'ABCDE') #=> ['AB', 'CD', 'E'] 30 | # slice(2, 'ABCDE', underfull_action: :fill, fill_value: 'X') 31 | # => ['AB', 'CD', 'EX'] 32 | # slice(2, 'ABCDE', underfull_action: :drop) 33 | # => ['AB', 'CD'] 34 | # 35 | # @diff 36 | # This method named +group+ in python-pwntools, but this is more similar to +Array#each_slice+ in ruby. 37 | def slice(n, str, underfull_action: :ignore, fill_value: nil) 38 | unless %i(ignore drop fill).include?(underfull_action) 39 | raise ArgumentError, 'underfull_action expect to be one of :ignore, :drop, and :fill' 40 | end 41 | 42 | sliced = str.chars.each_slice(n).map(&:join) 43 | case underfull_action 44 | when :drop 45 | sliced.pop unless sliced.last.size == n 46 | when :fill 47 | remain = n - sliced.last.size 48 | fill_value = fill_value.to_s 49 | raise ArgumentError, 'fill_value must be a character' unless fill_value.size == 1 50 | 51 | sliced.last.concat(fill_value * remain) 52 | end 53 | sliced 54 | end 55 | alias group slice 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/pwnlib/util/ruby.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | module Pwnlib 5 | module Util 6 | # module for some utilities for Ruby metaprogramming. 7 | module Ruby 8 | def self.private_class_method_block 9 | define_singleton_method(:singleton_method_added) do |m| 10 | private_class_method m 11 | end 12 | yield 13 | class << self 14 | remove_method(:singleton_method_added) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pwnlib/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | module Pwnlib 5 | # version of pwntools-ruby 6 | VERSION = '1.2.1' 7 | end 8 | -------------------------------------------------------------------------------- /pwntools.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require 'date' 7 | 8 | require 'pwnlib/version' 9 | 10 | Gem::Specification.new do |s| 11 | s.name = 'pwntools' 12 | s.version = ::Pwnlib::VERSION 13 | s.date = Date.today.to_s 14 | s.summary = 'pwntools' 15 | s.description = <<-EOS 16 | Rewrite https://github.com/Gallopsled/pwntools in ruby. 17 | Implement useful/easy functions first, 18 | try to be of ruby style and don't follow original pwntools everywhere. 19 | Would still try to have similar name whenever possible. 20 | EOS 21 | s.license = 'MIT' 22 | s.authors = ['peter50216@gmail.com', 'david942j@gmail.com', 'hanhan0912@gmail.com'] 23 | s.email = ['peter50216@gmail.com', 'david942j@gmail.com', 'hanhan0912@gmail.com'] 24 | s.homepage = 'https://github.com/peter50216/pwntools-ruby' 25 | s.files = Dir['lib/**/*.rb'] + %w(README.md Rakefile) 26 | s.test_files = Dir['test/**/*'] 27 | s.require_paths = ['lib'] 28 | 29 | s.required_ruby_version = '>= 2.4' 30 | 31 | s.add_runtime_dependency 'crabstone', '~> 4' 32 | s.add_runtime_dependency 'dentaku', '>= 2.0.11', '< 3.6.0' 33 | s.add_runtime_dependency 'elftools', '>= 1.0.1', '< 1.2.0' 34 | s.add_runtime_dependency 'keystone-engine', '~> 0.9' 35 | s.add_runtime_dependency 'method_source', '>= 0.9' 36 | s.add_runtime_dependency 'one_gadget', '>= 1.6.2', '< 1.9.0' 37 | s.add_runtime_dependency 'rainbow', '>= 2.2', '< 4.0' 38 | s.add_runtime_dependency 'ruby2ruby', '~> 2.4' 39 | s.add_runtime_dependency 'rubyserial', '~> 0.5' 40 | 41 | # TODO(david942j): check why ruby crash during testing if upgrade minitest to 5.10.2/3 42 | s.add_development_dependency 'minitest', '= 5.10.1' 43 | s.add_development_dependency 'pry', '~> 0.10' 44 | s.add_development_dependency 'rake', '~> 13.0' 45 | s.add_development_dependency 'rubocop', '~> 1' 46 | s.add_development_dependency 'simplecov', '~> 0.15', '< 0.18' 47 | s.add_development_dependency 'tty-platform', '~> 0.1' 48 | s.add_development_dependency 'yard', '~> 0.9' 49 | end 50 | -------------------------------------------------------------------------------- /tasks/shellcraft/x86.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :shellcraft do 4 | # Example: bundle exec rake 'shellcraft:x86[linux/*.rb]' 5 | desc 'Generate the almost same files under amd64/i386 that invoke methods of X86' 6 | GEN_PATH = File.join(__dir__, '..', '..', 'lib', 'pwnlib', 'shellcraft', 'generators').freeze 7 | task :x86, :pattern do |_t, args| 8 | pattern = File.join(GEN_PATH, 'x86', args.pattern) 9 | 10 | Dir.glob(pattern).each do |path| 11 | do_gen(Pathname.new(path).relative_path_from(Pathname.new(GEN_PATH)).to_s) 12 | end 13 | end 14 | 15 | TEMPLATE = <<-EOS 16 | # encoding: ASCII-8BIT 17 | # frozen_string_literal: true 18 | 19 | require 'pwnlib/shellcraft/generators/%s/%s/%s' 20 | require 'pwnlib/shellcraft/generators/x86/%s/%s' 21 | 22 | module Pwnlib 23 | module Shellcraft 24 | module Generators 25 | module %s 26 | module %s 27 | # @overload %s 28 | # 29 | # @see Generators::X86::%s#%s 30 | def %s(*args) 31 | context.local(arch: :%s) do 32 | cat X86::%s.%s(*args) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | EOS 41 | 42 | def do_gen(path) 43 | x86, dir, file = path.split('/') 44 | invalid(path) unless x86 == 'x86' && %w[linux common].include?(dir) && file.end_with?('.rb') 45 | func = file[0..-4] 46 | return if dir == func 47 | 48 | puts "Generating files of #{path.inspect}.." 49 | dir_ = dir.capitalize 50 | prototype = IO.binread(File.join(GEN_PATH, path)).scan(/^\s+def (#{func}.*)$/).flatten.last 51 | %w[amd64 i386].each do |arch| 52 | arch_ = arch.capitalize 53 | str = format(TEMPLATE, 54 | arch: arch, Arch: arch_, 55 | func: func, prototype: prototype, 56 | dir: dir, Dir: dir_) 57 | IO.binwrite(File.join(GEN_PATH, arch, dir, file), str) 58 | end 59 | end 60 | 61 | def invalid(path) 62 | raise ArgumentError, "Invalid path: #{path.inspect}." 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/abi_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/abi' 7 | require 'pwnlib/context' 8 | 9 | class AbiTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | ABI = ::Pwnlib::ABI 12 | 13 | def test_default 14 | context.local(arch: 'i386', os: 'linux') { assert_same ABI::DEFAULT[[32, 'i386', 'linux']], ABI::ABI.default } 15 | context.local(arch: 'amd64', os: 'linux') { assert_same ABI::DEFAULT[[64, 'amd64', 'linux']], ABI::ABI.default } 16 | end 17 | 18 | def test_syscall 19 | context.local(arch: 'i386', os: 'linux') { assert_same ABI::SYSCALL[[32, 'i386', 'linux']], ABI::ABI.syscall } 20 | context.local(arch: 'amd64', os: 'linux') { assert_same ABI::SYSCALL[[64, 'amd64', 'linux']], ABI::ABI.syscall } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/constants/constant_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/constants/constant' 7 | 8 | class ConstantTest < MiniTest::Test 9 | Constant = ::Pwnlib::Constants::Constant 10 | 11 | def test_methods 12 | a1 = Constant.new('a', 1) 13 | assert_equal(1, a1.to_i) 14 | assert_equal(3, a1 | 2) 15 | assert_operator(a1, :==, 1) 16 | # test coerce 17 | assert_operator(1, :==, a1) 18 | assert_operator(a1, :==, Constant.new('b', 1)) 19 | refute_operator(a1, :==, Constant.new('a', 3)) 20 | assert_equal(3, a1 | Constant.new('a', 2)) 21 | assert_equal(3, 2 | a1) 22 | assert_equal(1, 2 - a1) 23 | 24 | assert_equal(a1.method(:chr).call, "\x01") 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/constants/constants_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/constants/constants' 7 | require 'pwnlib/context' 8 | 9 | class ConstantsTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | Constants = ::Pwnlib::Constants 12 | 13 | def test_amd64 14 | context.local(arch: 'amd64') do 15 | assert_equal('Constant("SYS_read", 0x0)', Constants.SYS_read.inspect) 16 | assert_equal('__NR_arch_prctl', Constants.__NR_arch_prctl.to_s) 17 | assert_equal('Constant("(O_CREAT)", 0x40)', Constants.eval('O_CREAT').inspect) 18 | assert_equal('Constant("(O_CREAT | O_WRONLY)", 0x41)', Constants.eval('O_CREAT | O_WRONLY').inspect) 19 | err = assert_raises(::Pwnlib::Errors::ConstantNotFoundError) { Constants.eval('rax + rbx') } 20 | assert_equal('Undefined constant(s): rax, rbx', err.message) 21 | end 22 | end 23 | 24 | def test_i386 25 | context.local(arch: 'i386') do 26 | assert_equal('Constant("SYS_read", 0x3)', Constants.SYS_read.inspect) 27 | assert_equal('__NR_prctl', Constants.__NR_prctl.to_s) 28 | assert_equal('Constant("(O_CREAT)", 0x40)', Constants.eval('O_CREAT').inspect) 29 | assert_equal(0x40, Constants.method(:O_CREAT).call.to_i) 30 | # 2 < 3 31 | assert_operator(2, :<, Constants.SYS_read) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/context_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | 8 | class ContextTest < MiniTest::Test 9 | include ::Pwnlib::Context 10 | 11 | def test_update 12 | context.update(arch: :arm, os: 'windows') 13 | assert_equal('arm', context.arch) 14 | assert_equal('windows', context.os) 15 | end 16 | 17 | def test_local 18 | context.timeout = 1 19 | assert_equal(1, context.timeout) 20 | 21 | context.local(timeout: 2) do 22 | assert_equal(2, context.timeout) 23 | context.timeout = 3 24 | assert_equal(3, context.timeout) 25 | end 26 | 27 | assert_equal(1, context.timeout) 28 | 29 | assert_raises(RuntimeError) do 30 | context.local(timeout: 3) { raise 'QQ failed in block' } 31 | end 32 | 33 | assert_equal(1, context.timeout) 34 | end 35 | 36 | def test_clear 37 | default_arch = context.arch 38 | context.arch = 'arm' 39 | context.clear 40 | assert_equal(default_arch, context.arch) 41 | end 42 | 43 | def test_arch 44 | context.arch = 'mips' 45 | assert_equal('mips', context.arch) 46 | 47 | err = assert_raises(ArgumentError) { context.arch = 'shik' } 48 | assert_match(/arch must be one of/, err.message) 49 | assert_equal('mips', context.arch) 50 | 51 | context.clear 52 | assert_equal(32, context.bits) 53 | context.arch = :powerpc64 54 | assert_equal(64, context.bits) 55 | assert_equal('big', context.endian) 56 | end 57 | 58 | def test_bits 59 | context.bits = 64 60 | assert_equal(64, context.bits) 61 | 62 | err = assert_raises(ArgumentError) { context.bits = 0 } 63 | assert_match(/bits must be > 0/, err.message) 64 | end 65 | 66 | def test_bytes 67 | context.bytes = 8 68 | assert_equal(64, context.bits) 69 | assert_equal(8, context.bytes) 70 | 71 | context.bits = 32 72 | assert_equal(4, context.bytes) 73 | end 74 | 75 | def test_endian 76 | context.endian = 'le' 77 | assert_equal('little', context.endian) 78 | 79 | context.endian = :big 80 | assert_equal('big', context.endian) 81 | 82 | err = assert_raises(ArgumentError) { context.endian = 'SUPERBIG' } 83 | assert_match(/endian must be one of/, err.message) 84 | end 85 | 86 | def test_log_level 87 | context.log_level = 'error' 88 | assert_equal(Logger::ERROR, context.log_level) 89 | 90 | context.log_level = :fatal 91 | assert_equal(Logger::FATAL, context.log_level) 92 | 93 | context.log_level = 514 94 | assert_equal(514, context.log_level) 95 | 96 | err = assert_raises(ArgumentError) { context.log_level = 'BOOM' } 97 | assert_match(/log_level must be an integer or one of/, err.message) 98 | end 99 | 100 | def test_os 101 | context.os = 'windows' 102 | assert_equal('windows', context.os) 103 | 104 | context.os = :freebsd 105 | assert_equal('freebsd', context.os) 106 | 107 | err = assert_raises(ArgumentError) { context.os = 'deepblue' } 108 | assert_match(/os must be one of/, err.message) 109 | end 110 | 111 | def test_signed 112 | context.signed = true 113 | assert_equal(true, context.signed) 114 | 115 | context.signed = 'unsigned' 116 | assert_equal(false, context.signed) 117 | 118 | context.signed = :yes 119 | assert_equal(true, context.signed) 120 | 121 | err = assert_raises(ArgumentError) { context.signed = 'partial' } 122 | assert_match(/signed must be boolean or one of/, err.message) 123 | end 124 | 125 | def test_timeout 126 | context.timeout = 123 127 | assert_equal(123, context.timeout) 128 | end 129 | 130 | def test_newline 131 | context.newline = "\r\n" 132 | assert_equal("\r\n", context.newline) 133 | end 134 | 135 | def test_to_s 136 | assert_match(/\APwnlib::Context::ContextType\(.+\)\Z/, context.to_s) 137 | end 138 | 139 | def teardown 140 | context.clear 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /test/data/assembly/aarch64.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_arm64.c 2 | # ARM-64 3 | 2c: 09 00 38 d5 mrs x9, midr_el1 4 | 30: bf 40 00 d5 msr spsel, #0 5 | 34: 0c 05 13 d5 msr dbgdtrtx_el0, x12 6 | 38: 20 50 02 0e tbx v0.8b, {v1.16b, v2.16b, v3.16b}, v2.8b 7 | 3c: 20 e4 3d 0f scvtf v0.2s, v1.2s, #3 8 | 40: 00 18 a0 5f fmla s0, s0, v0.s[3] 9 | 44: a2 00 ae 9e fmov x2, v5.d[1] 10 | 48: 9f 37 03 d5 dsb nsh 11 | 4c: bf 33 03 d5 dmb osh 12 | 50: df 3f 03 d5 isb 13 | 54: 21 7c 02 9b mul x1, x1, x2 14 | 58: 21 7c 00 53 lsr w1, w1, #0 15 | 5c: 00 40 21 4b sub w0, w0, w1, uxtw 16 | 60: e1 0b 40 b9 ldr w1, [sp, #8] 17 | 64: 20 04 81 da cneg x0, x1, ne 18 | 68: 20 08 02 8b add x0, x1, x2, lsl #2 19 | 6c: 10 5b e8 3c ldr q16, [x24, w8, uxtw #4] 20 | -------------------------------------------------------------------------------- /test/data/assembly/amd64.s: -------------------------------------------------------------------------------- 1 | # simple move 2 | 0: b8 17 00 00 00 mov eax, 0x17 3 | 4 | # pwntools-python's shellcraft.sh() 5 | 0: 6a 68 push 0x68 6 | 2: 48 b8 2f 62 69 6e 2f 2f 2f 73 movabs rax, 0x732f2f2f6e69622f 7 | c: 50 push rax 8 | d: 48 89 e7 mov rdi, rsp 9 | 10: 68 72 69 01 01 push 0x1016972 10 | 15: 81 34 24 01 01 01 01 xor dword ptr [rsp], 0x1010101 11 | 1c: 31 f6 xor esi, esi 12 | 1e: 56 push rsi 13 | 1f: 6a 08 push 8 14 | 21: 5e pop rsi 15 | 22: 48 01 e6 add rsi, rsp 16 | 25: 56 push rsi 17 | 26: 48 89 e6 mov rsi, rsp 18 | 29: 31 d2 xor edx, edx 19 | 2b: 6a 3b push 0x3b 20 | 2d: 58 pop rax 21 | 2e: 0f 05 syscall 22 | -------------------------------------------------------------------------------- /test/data/assembly/arm.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_arm.c 2 | 1000: ed ff ff eb bl #0xfbc 3 | 1004: 04 e0 2d e5 str lr, [sp, #-4]! 4 | 1008: 00 00 00 00 andeq r0, r0, r0 5 | 100c: e0 83 22 e5 str r8, [r2, #-0x3e0]! 6 | 1010: f1 02 03 0e mcreq p2, #0, r0, c3, c1, #7 7 | 1014: 00 00 a0 e3 mov r0, #0 8 | 1018: 02 30 c1 e7 strb r3, [r1, r2] 9 | 101c: 00 00 53 e3 cmp r3, #0 10 | -------------------------------------------------------------------------------- /test/data/assembly/i386.s: -------------------------------------------------------------------------------- 1 | # simple move 2 | 0: b8 5d 00 00 00 mov eax, 0x5d 3 | 4 | # pwntools-python's shellcraft.sh() 5 | 0: 6a 68 push 0x68 6 | 2: 68 2f 2f 2f 73 push 0x732f2f2f 7 | 7: 68 2f 62 69 6e push 0x6e69622f 8 | c: 89 e3 mov ebx, esp 9 | e: 68 01 01 01 01 push 0x1010101 10 | 13: 81 34 24 72 69 01 01 xor dword ptr [esp], 0x1016972 11 | 1a: 31 c9 xor ecx, ecx 12 | 1c: 51 push ecx 13 | 1d: 6a 04 push 4 14 | 1f: 59 pop ecx 15 | 20: 01 e1 add ecx, esp 16 | 22: 51 push ecx 17 | 23: 89 e1 mov ecx, esp 18 | 25: 31 d2 xor edx, edx 19 | 27: 6a 0b push 0xb 20 | 29: 58 pop eax 21 | 2a: cd 80 int 0x80 22 | -------------------------------------------------------------------------------- /test/data/assembly/mips.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_mips.c 2 | 3 | # context: endian: big 4 | # !skip asm # because of keystone-engine/keystone#405 5 | # MIPS-32 (Big-endian) 6 | 1000: 0c 10 00 97 jal 0x40025c 7 | 1004: 00 00 00 00 nop 8 | 1008: 24 02 00 0c addiu $v0, $zero, 0xc 9 | 100c: 8f a2 00 00 lw $v0, ($sp) 10 | 1010: 34 21 34 56 ori $at, $at, 0x3456 11 | 12 | # context: endian: big 13 | # Copied from above, ignored branch instructions 14 | 1008: 24 02 00 0c addiu $v0, $zero, 0xc 15 | 100c: 8f a2 00 00 lw $v0, ($sp) 16 | 1010: 34 21 34 56 ori $at, $at, 0x3456 17 | -------------------------------------------------------------------------------- /test/data/assembly/mips64.s: -------------------------------------------------------------------------------- 1 | # context: endian: little 2 | # These tests are fetched from Capstone's test_mips.c 3 | # MIPS-64-EL (Little-endian) 4 | 1000: 56 34 21 34 ori $at, $at, 0x3456 5 | 1004: c2 17 01 00 srl $v0, $at, 0x1f 6 | 1008: 70 00 b2 ff sd $s2, 0x70($sp) 7 | -------------------------------------------------------------------------------- /test/data/assembly/powerpc.s: -------------------------------------------------------------------------------- 1 | # context: endian: big 2 | # This test is (almost) same as powerpc64.s 3 | 1000: 43 20 0c 07 bdnzla+ 0xc04 4 | 1004: 41 56 ff 17 bdztla 4*cr5+eq, 0xffffffffffffff14 5 | 1008: 80 20 00 00 lwz 1, 0(0) 6 | 1010: 80 3f 00 00 lwz 1, 0(31) 7 | 1014: 10 43 23 0e vpkpx 2, 3, 4 8 | 1018: d0 44 00 80 stfs 2, 0x80(4) 9 | 101c: 4c 43 22 02 crand 2, 3, 4 10 | 1020: 2d 03 00 80 cmpwi cr2, 3, 0x80 11 | 1024: 7c 43 20 14 addc 2, 3, 4 12 | 1028: 7c 43 20 93 mulhd. 2, 3, 4 13 | 102c: 4f 20 00 21 bdnzlrl+ 14 | 1030: 4c c8 00 21 bgelrl- cr2 15 | 1034: 40 82 00 14 bne 0x1044 16 | 17 | # This instruction in ppc32 only 18 | 0: 7c 21 04 a6 mfsr 1, 1 19 | -------------------------------------------------------------------------------- /test/data/assembly/powerpc64.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_ppc.c 2 | 3 | # context: endian: big 4 | # !skip asm 5 | # PPC-64 6 | 1000: 43 20 0c 07 bdnzla+ 0xc04 7 | 1004: 41 56 7f 17 bdztla 4*cr5+eq, 0x7f14 8 | # Inconsistent output between capstone3 and later versions, skip 9 | ; 1008: 80 20 00 00 lwz r1, 0(0) 10 | ; 100c: 80 3f 00 00 lwz r1, 0(r31) 11 | 1008: 10 43 23 0e vpkpx v2, v3, v4 12 | 100c: d0 44 00 80 stfs f2, 0x80(r4) 13 | 1010: 4c 43 22 02 crand 2, 3, 4 14 | 1014: 2d 03 00 80 cmpwi cr2, r3, 0x80 15 | 1018: 7c 43 20 14 addc r2, r3, r4 16 | 101c: 7c 43 20 93 mulhd. r2, r3, r4 17 | 1020: 4f 20 00 21 bdnzlrl+ 18 | 1024: 4c c8 00 21 bgelrl- cr2 19 | 1028: 40 82 00 14 bne 0x103c 20 | 21 | # context: endian: big 22 | # !skip disasm 23 | # PPC-64 24 | 1000: 43 20 0c 07 bdnzla+ 0xc04 25 | 1004: 41 56 7f 17 bdztla 4*cr5+eq, 0x7f14 26 | 1008: 80 20 00 00 lwz 1, 0(0) 27 | 1010: 80 3f 00 00 lwz 1, 0(31) 28 | 1014: 10 43 23 0e vpkpx 2, 3, 4 29 | 1018: d0 44 00 80 stfs 2, 0x80(4) 30 | 101c: 4c 43 22 02 crand 2, 3, 4 31 | 1020: 2d 03 00 80 cmpwi cr2, 3, 0x80 32 | 1024: 7c 43 20 14 addc 2, 3, 4 33 | 1028: 7c 43 20 93 mulhd. 2, 3, 4 34 | 102c: 4f 20 00 21 bdnzlrl+ 35 | 1030: 4c c8 00 21 bgelrl- cr2 36 | 1034: 40 82 00 14 bne 0x1044 37 | -------------------------------------------------------------------------------- /test/data/assembly/sparc.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_sparc.c 2 | 3 | # !skip asm # because of keystone-engine/keystone#405 4 | 1000: 80 a0 40 02 cmp %g1, %g2 5 | 1004: 85 c2 60 08 jmpl %o1+8, %g2 6 | 1008: 85 e8 20 01 restore %g0, 1, %g2 7 | 100c: 81 e8 00 00 restore 8 | 1010: 90 10 20 01 mov 1, %o0 9 | 1014: d5 f6 10 16 casx [%i0], %l6, %o2 10 | 1018: 21 00 00 0a sethi 0xa, %l0 11 | 101c: 86 00 40 02 add %g1, %g2, %g3 12 | 1020: 01 00 00 00 nop 13 | 1024: 12 bf ff ff bne 0x1020 14 | 1028: 10 bf ff ff ba 0x1024 15 | 102c: a0 02 00 09 add %o0, %o1, %l0 16 | 1030: 0d bf ff ff fbg 0x102c 17 | 1034: d4 20 40 00 st %o2, [%g1] 18 | 1038: d4 4e 00 16 ldsb [%i0+%l6], %o2 19 | # The output between objdump/llvm/capstone is inconsistent 20 | ; 103c: 2a c2 80 03 brnz,a,pn %o2, 0x1048 21 | 22 | # Copied from above, ignored branch instructions 23 | 1000: 80 a0 40 02 cmp %g1, %g2 24 | 1004: 85 e8 20 01 restore %g0, 1, %g2 25 | 1008: 81 e8 00 00 restore 26 | 100c: 90 10 20 01 mov 1, %o0 27 | 1010: d5 f6 10 16 casx [%i0], %l6, %o2 28 | 1014: 21 00 00 0a sethi 0xa, %l0 29 | 1018: 86 00 40 02 add %g1, %g2, %g3 30 | 101c: 01 00 00 00 nop 31 | 1020: a0 02 00 09 add %o0, %o1, %l0 32 | 1024: d4 20 40 00 st %o2, [%g1] 33 | 1028: d4 4e 00 16 ldsb [%i0+%l6], %o2 34 | -------------------------------------------------------------------------------- /test/data/assembly/sparc64.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_sparc.c 2 | 1000: 81 a8 0a 24 fcmps %f0, %f4 3 | 1004: 89 a0 10 20 fstox %f0, %f4 4 | 1008: 89 a0 1a 60 fqtoi %f0, %f4 5 | 100c: 89 a0 00 e0 fnegq %f0, %f4 6 | -------------------------------------------------------------------------------- /test/data/assembly/thumb.s: -------------------------------------------------------------------------------- 1 | # These tests are fetched from Capstone's test_arm.c 2 | # Thumb 3 | # PC-relative instructions are buggy in Capstone3, two lines are commented. 4 | 80001000: 60 f9 1f 04 vld3.8 {d16, d17, d18}, [r0:0x40] 5 | 80001004: e0 f9 4f 07 vld4.16 {d16[1], d17[1], d18[1], d19[1]}, [r0] 6 | 80001008: 70 47 bx lr 7 | ; 8000100a: 00 f0 10 e8 blx #0x8000102c 8 | 8000100a: eb 46 mov fp, sp 9 | 8000100c: 83 b0 sub sp, #0xc 10 | 8000100e: c9 68 ldr r1, [r1, #0xc] 11 | ; 80001010: 1f b1 cbz r7, #0x8000101e 12 | 80001010: 30 bf wfi 13 | 80001012: af f3 20 84 cpsie.w f 14 | 80001016: 52 f8 23 f0 ldr.w pc, [r2, r3, lsl #2] 15 | 16 | # Thumb-mixed 17 | 80001000: d1 e8 00 f0 tbb [r1, r0] 18 | 80001004: f0 24 movs r4, #0xf0 19 | 80001006: 04 07 lsls r4, r0, #0x1c 20 | 80001008: 1f 3c subs r4, #0x1f 21 | 8000100a: f2 c0 stm r0!, {r1, r4, r5, r6, r7} 22 | 8000100c: 00 00 movs r0, r0 23 | 8000100e: 4f f0 00 01 mov.w r1, #0 24 | 80001012: 46 6c ldr r6, [r0, #0x44] 25 | 26 | # Thumb-2 & register named with numbers 27 | # An `iteet` instruction is removed to make the `it` instruction valid 28 | 80001000: 4f f0 00 01 mov.w r1, #0 29 | 80001004: bd e8 00 88 pop.w {fp, pc} 30 | 80001008: d1 e8 00 f0 tbb [r1, r0] 31 | 8000100c: 18 bf it ne 32 | ; 8000100e: ad bf iteet ge 33 | 8000100e: f3 ff 0b 0c vdupne.8 d16, d11[1] 34 | 80001012: 86 f3 00 89 msr cpsr_fc, r6 35 | 80001016: 80 f3 00 8c msr apsr_nzcvqg, r0 36 | 8000101a: 4f fa 99 f6 sxtb.w r6, sb, ror #8 37 | 8000101e: d0 ff a2 01 vaddw.u16 q8, q8, d18 38 | -------------------------------------------------------------------------------- /test/data/echo.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'socket' 5 | 6 | server = TCPServer.open('127.0.0.1', 0) 7 | 8 | $stdout.puts "Start with port #{server.addr[1]}" 9 | $stdout.flush 10 | 11 | client = server.accept 12 | s = client.gets 13 | client.puts(s) 14 | client.close 15 | 16 | $stdout.puts 'Bye!' 17 | -------------------------------------------------------------------------------- /test/data/elfs/Makefile: -------------------------------------------------------------------------------- 1 | source=source.cpp 2 | FLAGS= 3 | PIE_FLAGS=-pie 4 | NOPIE_FLAGS=-no-pie 5 | FULL_RELRO_FLAGS=-Wl,-z,relro,-z,now 6 | PARTIAL_RELRO_FLAGS=-Wl,-z,relro 7 | NO_RELRO_FLAGS=-Wl,-z,norelro 8 | STATIC_FLAGS=-static 9 | TARGETS=amd64 i386 10 | .PHONY: clean 11 | all: ${TARGETS} 12 | 13 | amd64: ${source} 14 | g++ -m64 ${source} -o amd64.frelro.elf ${FLAGS} ${FULL_RELRO_FLAGS} ${NOPIE_FLAGS} 15 | g++ -m64 ${source} -o amd64.frelro.pie.elf ${FLAGS} ${FULL_RELRO_FLAGS} ${PIE_FLAGS} 16 | g++ -m64 ${source} -o amd64.prelro.elf ${FLAGS} ${PARTIAL_RELRO_FLAGS} ${NOPIE_FLAGS} 17 | g++ -m64 ${source} -o amd64.nrelro.elf ${FLAGS} ${NO_RELRO_FLAGS} ${NOPIE_FLAGS} 18 | g++ -m64 ${source} -o amd64.static.elf ${FLAGS} ${STATIC_FLAGS} ${NOPIE_FLAGS} 19 | i386: ${source} 20 | g++ -m32 ${source} -o i386.prelro.elf ${FLAGS} ${PARTIAL_RELRO_FLAGS} ${NOPIE_FLAGS} 21 | g++ -m32 ${source} -o i386.frelro.pie.elf ${FLAGS} ${FULL_RELRO_FLAGS} ${PIE_FLAGS} 22 | 23 | clean: 24 | rm -f *.elf 25 | -------------------------------------------------------------------------------- /test/data/elfs/amd64.frelro.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/amd64.frelro.elf -------------------------------------------------------------------------------- /test/data/elfs/amd64.frelro.pie.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/amd64.frelro.pie.elf -------------------------------------------------------------------------------- /test/data/elfs/amd64.nrelro.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/amd64.nrelro.elf -------------------------------------------------------------------------------- /test/data/elfs/amd64.prelro.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/amd64.prelro.elf -------------------------------------------------------------------------------- /test/data/elfs/amd64.static.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/amd64.static.elf -------------------------------------------------------------------------------- /test/data/elfs/i386.frelro.pie.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/i386.frelro.pie.elf -------------------------------------------------------------------------------- /test/data/elfs/i386.prelro.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/elfs/i386.prelro.elf -------------------------------------------------------------------------------- /test/data/elfs/source.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | char s[101]; 4 | void func() { 5 | static int test = 0; 6 | test++; 7 | puts("In func:"); 8 | printf("test = %d\n", test); 9 | } 10 | int main() { 11 | fgets(s, 100, stdin); 12 | printf("%s", s); 13 | int n; 14 | scanf("%d", &n); 15 | while(n--) { 16 | func(); 17 | } 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /test/data/flag: -------------------------------------------------------------------------------- 1 | flag{pwntools_ruby} 2 | -------------------------------------------------------------------------------- /test/data/lib32/ld.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/lib32/ld.so.2 -------------------------------------------------------------------------------- /test/data/lib32/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/lib32/libc.so.6 -------------------------------------------------------------------------------- /test/data/lib64/ld.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/lib64/ld.so.2 -------------------------------------------------------------------------------- /test/data/lib64/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/lib64/libc.so.6 -------------------------------------------------------------------------------- /test/data/victim.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | setvbuf(stdout, NULL, _IONBF, 0); 5 | printf("%p\n", __builtin_return_address(0)); 6 | scanf("%c"); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /test/data/victim32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/victim32 -------------------------------------------------------------------------------- /test/data/victim64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter50216/pwntools-ruby/1c16ec965cdf7432eed2b0d8bbb3fd5492831482/test/data/victim64 -------------------------------------------------------------------------------- /test/dynelf_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'open3' 5 | 6 | require 'tty-platform' 7 | 8 | require 'test_helper' 9 | 10 | require 'pwnlib/context' 11 | require 'pwnlib/dynelf' 12 | require 'pwnlib/elf/elf' 13 | 14 | class DynELFTest < MiniTest::Test 15 | include ::Pwnlib 16 | include ::Pwnlib::Context 17 | include ::Pwnlib::ELF 18 | 19 | def setup 20 | linux_only 21 | end 22 | 23 | # popen victim with specific libc.so.6 24 | def popen_victim(b) 25 | lib_path = File.expand_path("data/lib#{b}/", __dir__) 26 | libc_path = File.expand_path('libc.so.6', lib_path) 27 | ld_path = File.expand_path('ld.so.2', lib_path) 28 | victim_path = File.expand_path("data/victim#{b}", __dir__) 29 | 30 | Open3.popen2("#{ld_path} --library-path #{lib_path} #{victim_path}") do |i, o, t| 31 | main_ra = Integer(o.readline) 32 | mem = open("/proc/#{t.pid}/mem", 'rb') 33 | d = DynELF.new(main_ra) do |addr| 34 | mem.seek(addr) 35 | mem.getc 36 | end 37 | 38 | yield d, { libc: libc_path, main_ra: main_ra, pid: t.pid } 39 | 40 | mem.close 41 | i.write('bye') 42 | end 43 | end 44 | 45 | def test_find_base 46 | [32, 64].each do |b| 47 | popen_victim(b) do |d, options| 48 | main_ra = options[:main_ra] 49 | realbase = nil 50 | IO.readlines("/proc/#{options[:pid]}/maps").map(&:split).each do |s| 51 | st, ed = s[0].split('-').map { |x| x.to_i(16) } 52 | next unless main_ra.between?(st, ed) 53 | 54 | realbase = st 55 | break 56 | end 57 | refute_nil(realbase) 58 | assert_equal(realbase, d.libbase) 59 | end 60 | end 61 | end 62 | 63 | def test_lookup 64 | [32, 64].each do |b| 65 | popen_victim(b) do |d, options| 66 | assert_nil(d.lookup('pipi_hao_wei!')) 67 | elf = ELF.new(options[:libc], checksec: false) 68 | %i(system open read write execve printf puts sprintf mmap mprotect).each do |sym| 69 | assert_equal(d.libbase + elf.symbols[sym], d.lookup(sym)) 70 | end 71 | end 72 | end 73 | end 74 | 75 | def test_build_id 76 | [ 77 | [32, 'ac333186c6b532511a68d16aca4c61422eb772da', 'i386'], 78 | [64, '088a6e00a1814622219f346b41e775b8dd46c518', 'amd64'] 79 | ].each do |b, answer, arch| 80 | context.local(arch: arch) do 81 | popen_victim(b) { |d| assert_equal(answer, d.build_id) } 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/ext_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/ext/array' 7 | require 'pwnlib/ext/integer' 8 | require 'pwnlib/ext/string' 9 | 10 | class ExtTest < MiniTest::Test 11 | # Thought that test one method in each module for each type is enough, since it's quite stupid (and meaningless) to 12 | # copy the list of proxied functions to here... 13 | def test_ext_string 14 | assert_equal(0x4142, 'AB'.u16(endian: 'be')) 15 | assert_equal([1, 1, 0, 0, 0, 1, 0, 0], "\xC4".bits) 16 | end 17 | 18 | def test_ext_integer 19 | assert_equal('AB', 0x4241.p16) 20 | assert_equal([0, 0, 1, 1, 0, 1, 0, 0], 0x34.bits) 21 | assert_equal(2**31, 1.bitswap) 22 | end 23 | 24 | def test_ext_array 25 | assert_equal("\xfe", [1, 1, 1, 1, 1, 1, 1, 0].unbits) 26 | assert_equal("XX\xef\xbe\xad\xdeXX", ['XX', 0xdeadbeef, 'XX'].flat) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/files/use_pwn.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | # Make sure we're using local copy for local testing. 5 | $LOAD_PATH.unshift File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib')) 6 | 7 | require 'pwn' 8 | 9 | context[arch: 'amd64'] 10 | 11 | raise 'pack fail' unless pack(1) == "\x01\0\0\0\0\0\0\0" 12 | raise 'not unique context' unless ::Pwnlib::Util::Fiddling.__send__(:context).equal?(context) 13 | raise 'not unique context' unless ::Pwnlib::Context.context.equal?(context) 14 | 15 | # Make sure things aren't polluting Object 16 | begin 17 | 1.__send__(:context) 18 | raise 'context polluting Object.' 19 | rescue NoMethodError 20 | puts 'good' 21 | end 22 | 23 | begin 24 | '1'.__send__(:context) 25 | raise 'context polluting Object.' 26 | rescue NoMethodError 27 | puts 'good' 28 | end 29 | 30 | # Make sure we can use Util::xxx::yyy directly 31 | raise 'pack fail' unless Util::Packing.pack(1) == "\x01\0\0\0\0\0\0\0" 32 | -------------------------------------------------------------------------------- /test/files/use_pwnlib.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | # Make sure we're using local copy for local testing. 5 | $LOAD_PATH.unshift File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib')) 6 | 7 | # TODO(Darkpi): Should we make sure ALL module works? (maybe we should). 8 | require 'pwnlib/util/packing' 9 | 10 | raise 'call from module fail' unless ::Pwnlib::Util::Packing.p8(0x61) == 'a' 11 | 12 | include ::Pwnlib::Util::Packing 13 | raise 'include module and call fail' unless p8(0x61) == 'a' 14 | 15 | begin 16 | ::Pwnlib::Util::Packing.context 17 | raise 'context public in Pwnlib module' 18 | rescue NoMethodError 19 | puts 'good' 20 | end 21 | -------------------------------------------------------------------------------- /test/full_file_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'open3' 5 | 6 | require 'test_helper' 7 | 8 | class FullFileTest < MiniTest::Test 9 | parallelize_me! 10 | Dir['test/files/*.rb'].each do |f| 11 | fn = File.basename(f, '.rb') 12 | define_method("test_#{fn}") do 13 | _, stderr, status = Open3.capture3('ruby', f, binmode: true) 14 | assert(status.success?, stderr) 15 | end 16 | end 17 | 18 | def test_ruby_cli 19 | _, stderr, status = Open3.capture3('ruby', '-e', 'require "pwn"', binmode: true) 20 | assert(status.success?, stderr) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/logger_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'open3' 5 | require 'tempfile' 6 | 7 | require 'test_helper' 8 | 9 | require 'pwnlib/context' 10 | require 'pwnlib/logger' 11 | 12 | class LoggerTest < MiniTest::Test 13 | include ::Pwnlib::Context 14 | include ::Pwnlib::Logger 15 | 16 | def setup 17 | @logger = ::Pwnlib::Logger::LoggerType.new 18 | class << @logger 19 | def add(*) 20 | clear 21 | super 22 | @logdev.string 23 | end 24 | 25 | def indented(*, **) 26 | clear 27 | super 28 | @logdev.string 29 | end 30 | 31 | def clear 32 | @logdev = StringIO.new 33 | end 34 | end 35 | end 36 | 37 | def test_log 38 | str = 'darkhh i4 so s4d' 39 | context.local(log_level: DEBUG) do 40 | %w(DEBUG INFO WARN ERROR FATAL).each do |type| 41 | assert_equal("[#{type}] #{str}\n", @logger.public_send(type.downcase, str)) 42 | assert_equal("[#{type}] #{str}\n", @logger.public_send(type.downcase) { str }) 43 | end 44 | end 45 | 46 | assert_empty(@logger.debug(str)) 47 | assert_empty(@logger.debug { str }) 48 | %w(INFO WARN ERROR FATAL).each do |type| 49 | assert_equal("[#{type}] #{str}\n", @logger.public_send(type.downcase, str)) 50 | assert_equal("[#{type}] #{str}\n", @logger.public_send(type.downcase) { str }) 51 | end 52 | end 53 | 54 | def test_indented 55 | assert_silent { log.indented('darkhh', level: DEBUG) } 56 | assert_empty(@logger.indented('A', level: DEBUG)) 57 | 58 | data = ['meow', 'meow meow', 'meowmeowmeow'].join("\n") 59 | assert_equal(<<-EOS, @logger.indented(data, level: INFO)) 60 | meow 61 | meow meow 62 | meowmeowmeow 63 | EOS 64 | end 65 | 66 | def test_dump 67 | x = 2 68 | y = 3 69 | assert_equal(<<-EOS, @logger.dump(x + y, x * y)) 70 | [DUMP] (x + y) = 5, (x * y) = 6 71 | EOS 72 | 73 | libc = 0x7fc0bdd13000 74 | # check if source code parsing works good 75 | msg = @logger.dump( 76 | libc # comment is ok 77 | .to_s(16), 78 | libc - libc * 1 79 | ) 80 | assert_equal(<<-EOS, msg) 81 | [DUMP] libc.to_s(16) = "7fc0bdd13000", (libc - (libc * 1)) = 0 82 | EOS 83 | 84 | libc = 0x7fc0bdd13000 85 | assert_equal(<<-EOS, @logger.dump { libc.to_s(16) }) 86 | [DUMP] libc.to_s(16) = "7fc0bdd13000" 87 | EOS 88 | 89 | res = @logger.dump do 90 | libc = 12_345_678 91 | libc <<= 12 92 | # comments will be ignored 93 | libc.to_s # dummy line 94 | libc.to_s(16) 95 | end 96 | assert_equal(<<-EOS, res) 97 | [DUMP] libc = 12345678 98 | libc = (libc << 12) 99 | libc.to_s 100 | libc.to_s(16) = "bc614e000" 101 | EOS 102 | 103 | lib_path = File.expand_path(File.join(__dir__, '..', 'lib')) 104 | f = Tempfile.new(['dump', '.rb']) 105 | begin 106 | f.write <<~EOS 107 | $LOAD_PATH.unshift #{lib_path.inspect} 108 | require 'pwn' 109 | FileUtils.remove(__FILE__) 110 | log.dump 1337 111 | EOS 112 | f.close 113 | _, stderr, status = Open3.capture3('ruby', f.path, binmode: true) 114 | assert(status.success?, stderr) 115 | ensure 116 | f.close 117 | f.unlink 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /test/memleak_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'open3' 5 | 6 | require 'tty-platform' 7 | 8 | require 'test_helper' 9 | 10 | require 'pwnlib/memleak' 11 | 12 | class MemLeakTest < MiniTest::Test 13 | def setup 14 | @victim = IO.binread(File.expand_path('data/victim32', __dir__)) 15 | @leak = ::Pwnlib::MemLeak.new { |addr| @victim[addr] } 16 | end 17 | 18 | def test_n 19 | assert_equal("\x7fELF", @leak.n(0, 4)) 20 | assert_equal(@victim[0xf0, 0x20], @leak.n(0xf0, 0x20)) 21 | assert_equal(@victim[514, 0x20], @leak.n(514, 0x20)) 22 | end 23 | 24 | def test_b 25 | assert_equal(::Pwnlib::Util::Packing.u8(@victim[0x100]), @leak.b(0x100)) 26 | assert_equal(::Pwnlib::Util::Packing.u8(@victim[514]), @leak.b(514)) 27 | end 28 | 29 | def test_w 30 | assert_equal(::Pwnlib::Util::Packing.u16(@victim[0x100, 2]), @leak.w(0x100)) 31 | assert_equal(::Pwnlib::Util::Packing.u16(@victim[514, 2]), @leak.w(514)) 32 | end 33 | 34 | def test_d 35 | assert_equal(::Pwnlib::Util::Packing.u32(@victim[0, 4]), @leak.d(0)) 36 | assert_equal(::Pwnlib::Util::Packing.u32(@victim[0x100, 4]), @leak.d(0x100)) 37 | assert_equal(::Pwnlib::Util::Packing.u32(@victim[514, 4]), @leak.d(514)) 38 | end 39 | 40 | def test_q 41 | assert_equal(::Pwnlib::Util::Packing.u64(@victim[0x100, 8]), @leak.q(0x100)) 42 | assert_equal(::Pwnlib::Util::Packing.u64(@victim[514, 8]), @leak.q(514)) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/reg_sort_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/reg_sort' 7 | 8 | class RegSortTest < MiniTest::Test 9 | include ::Pwnlib::RegSort 10 | 11 | def setup 12 | @regs = %w(a b c d x y z) 13 | end 14 | 15 | def test_normal 16 | assert_equal([['mov', 'a', 1], ['mov', 'b', 2]], regsort({ a: 1, b: 2 }, @regs)) 17 | end 18 | 19 | def test_post_mov 20 | assert_equal([['mov', 'a', 1], %w(mov b a)], regsort({ a: 1, b: 1 }, @regs)) 21 | assert_equal([%w(mov c a), ['mov', 'a', 1], %w(mov b a)], regsort({ a: 1, b: 1, c: 'a' }, @regs)) 22 | end 23 | 24 | def test_pseudoforest 25 | # only one connected component 26 | assert_equal([%w(mov b a), ['mov', 'a', 1]], regsort({ a: 1, b: 'a' }, @regs)) 27 | assert_equal([['mov', 'c', 3], %w(xchg a b)], regsort({ a: 'b', b: 'a', c: 3 }, @regs)) 28 | assert_equal([%w(mov c b), %w(xchg a b)], regsort({ a: 'b', b: 'a', c: 'b' }, @regs)) 29 | assert_equal([%w(mov x 1), %w(mov y z), %w(mov z c), %w(xchg a b), %w(xchg b c)], 30 | regsort({ a: 'b', b: 'c', c: 'a', x: '1', y: 'z', z: 'c' }, @regs)) 31 | 32 | # more than one connected components 33 | assert_equal([%w(xchg a b), %w(xchg c d)], regsort({ a: 'b', b: 'a', c: 'd', d: 'c' }, @regs)) 34 | assert_equal([%w(mov c b), %w(mov d b), %w(mov z x), %w(xchg a b), %w(xchg x y)], 35 | regsort({ a: 'b', b: 'a', c: 'b', d: 'b', x: 'y', y: 'x', z: 'x' }, @regs)) 36 | end 37 | 38 | def test_raise 39 | err = assert_raises(ArgumentError) do 40 | regsort({ a: 1 }, ['b']) 41 | end 42 | assert_match(/Unknown register!/, err.message) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/runner_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/abi' 7 | require 'pwnlib/runner' 8 | require 'pwnlib/shellcraft/shellcraft' 9 | 10 | class RunnerTest < MiniTest::Test 11 | include ::Pwnlib::Context 12 | 13 | def setup 14 | linux_only 'Runner can only be used on Linux' 15 | end 16 | 17 | def shellcraft 18 | ::Pwnlib::Shellcraft::Shellcraft.instance 19 | end 20 | 21 | def test_run_assembly 22 | %w[i386 amd64].each do |arch| 23 | context.local(arch: arch) do 24 | r = ::Pwnlib::Runner.run_assembly( 25 | shellcraft.pushstr('run_assembly') + 26 | shellcraft.syscall('SYS_write', 1, ::Pwnlib::ABI::ABI.default.stack_pointer, 12) + 27 | shellcraft.exit(0) 28 | ) 29 | assert_equal('run_assembly', r.recvn(12)) 30 | # Test if reach EOF 31 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { r.recv } 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/shellcraft/infloop_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class InfloopTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_match(/\Ainfloop_\d+:\n jmp infloop_\d+\n\Z/, @shellcraft.infloop) 19 | end 20 | end 21 | 22 | def test_i386 23 | context.local(arch: 'i386') do 24 | assert_match(/\Ainfloop_\d+:\n jmp infloop_\d+\n\Z/, @shellcraft.infloop) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/shellcraft/linux/cat_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class CatTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.cat('flag')) 19 | /* push "flag\x00" */ 20 | push 0x67616c66 21 | /* call open("rsp", "O_RDONLY", 0) */ 22 | push 2 /* (SYS_open) */ 23 | pop rax 24 | mov rdi, rsp 25 | xor esi, esi /* (O_RDONLY) */ 26 | cdq /* rdx=0 */ 27 | syscall 28 | /* call sendfile(1, "rax", 0, 2147483647) */ 29 | push 1 30 | pop rdi 31 | mov rsi, rax 32 | push 0x28 /* (SYS_sendfile) */ 33 | pop rax 34 | mov r10d, 0x7fffffff 35 | cdq /* rdx=0 */ 36 | syscall 37 | EOS 38 | assert_equal(<<-'EOS', @shellcraft.cat('flag', fd: 2)) 39 | /* push "flag\x00" */ 40 | push 0x67616c66 41 | /* call open("rsp", "O_RDONLY", 0) */ 42 | push 2 /* (SYS_open) */ 43 | pop rax 44 | mov rdi, rsp 45 | xor esi, esi /* (O_RDONLY) */ 46 | cdq /* rdx=0 */ 47 | syscall 48 | /* call sendfile(2, "rax", 0, 2147483647) */ 49 | push 2 50 | pop rdi 51 | mov rsi, rax 52 | push 0x28 /* (SYS_sendfile) */ 53 | pop rax 54 | mov r10d, 0x7fffffff 55 | cdq /* rdx=0 */ 56 | syscall 57 | EOS 58 | end 59 | end 60 | 61 | def test_i386 62 | context.local(arch: 'i386') do 63 | assert_equal(<<-'EOS', @shellcraft.cat('flag')) 64 | /* push "flag\x00" */ 65 | push 1 66 | dec byte ptr [esp] 67 | push 0x67616c66 68 | /* call open("esp", "O_RDONLY", 0) */ 69 | push 5 /* (SYS_open) */ 70 | pop eax 71 | mov ebx, esp 72 | xor ecx, ecx /* (O_RDONLY) */ 73 | cdq /* edx=0 */ 74 | int 0x80 75 | /* call sendfile(1, "eax", 0, 2147483647) */ 76 | push 1 77 | pop ebx 78 | mov ecx, eax 79 | xor eax, eax 80 | mov al, 0xbb /* (SYS_sendfile) */ 81 | mov esi, 0x7fffffff 82 | cdq /* edx=0 */ 83 | int 0x80 84 | EOS 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/shellcraft/linux/ls_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class LsTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.ls) 19 | /* push ".\x00" */ 20 | push 0x2e 21 | /* call open("rsp", 0, 0) */ 22 | push 2 /* (SYS_open) */ 23 | pop rax 24 | mov rdi, rsp 25 | xor esi, esi /* 0 */ 26 | cdq /* rdx=0 */ 27 | syscall 28 | /* call getdents("rax", "rsp", 4096) */ 29 | mov rdi, rax 30 | push 0x4e /* (SYS_getdents) */ 31 | pop rax 32 | mov rsi, rsp 33 | xor edx, edx 34 | mov dh, 0x1000 >> 8 35 | syscall 36 | /* call write(1, "rsp", "rax") */ 37 | push 1 38 | pop rdi 39 | mov rsi, rsp 40 | mov rdx, rax 41 | push 1 /* (SYS_write) */ 42 | pop rax 43 | syscall 44 | EOS 45 | assert_equal(<<-'EOS', @shellcraft.ls('/usr/bin')) 46 | /* push "/usr/bin\x00" */ 47 | push 1 48 | dec byte ptr [rsp] 49 | mov rax, 0x6e69622f7273752f 50 | push rax 51 | /* call open("rsp", 0, 0) */ 52 | push 2 /* (SYS_open) */ 53 | pop rax 54 | mov rdi, rsp 55 | xor esi, esi /* 0 */ 56 | cdq /* rdx=0 */ 57 | syscall 58 | /* call getdents("rax", "rsp", 4096) */ 59 | mov rdi, rax 60 | push 0x4e /* (SYS_getdents) */ 61 | pop rax 62 | mov rsi, rsp 63 | xor edx, edx 64 | mov dh, 0x1000 >> 8 65 | syscall 66 | /* call write(1, "rsp", "rax") */ 67 | push 1 68 | pop rdi 69 | mov rsi, rsp 70 | mov rdx, rax 71 | push 1 /* (SYS_write) */ 72 | pop rax 73 | syscall 74 | EOS 75 | end 76 | end 77 | 78 | def test_i386 79 | context.local(arch: 'i386') do 80 | assert_equal(<<-'EOS', @shellcraft.ls) 81 | /* push ".\x00" */ 82 | push 0x2e 83 | /* call open("esp", 0, 0) */ 84 | push 5 /* (SYS_open) */ 85 | pop eax 86 | mov ebx, esp 87 | xor ecx, ecx /* 0 */ 88 | cdq /* edx=0 */ 89 | int 0x80 90 | /* call getdents("eax", "esp", 4096) */ 91 | mov ebx, eax 92 | xor eax, eax 93 | mov al, 0x8d /* (SYS_getdents) */ 94 | mov ecx, esp 95 | xor edx, edx 96 | mov dh, 0x1000 >> 8 97 | int 0x80 98 | /* call write(1, "esp", "eax") */ 99 | push 1 100 | pop ebx 101 | mov ecx, esp 102 | mov edx, eax 103 | push 4 /* (SYS_write) */ 104 | pop eax 105 | int 0x80 106 | EOS 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/shellcraft/linux/sh_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class ShTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.sh) 19 | /* push "/bin///sh\x00" */ 20 | push 0x68 21 | mov rax, 0x732f2f2f6e69622f 22 | push rax 23 | 24 | /* call execve("rsp", 0, 0) */ 25 | push 0x3b /* (SYS_execve) */ 26 | pop rax 27 | mov rdi, rsp 28 | xor esi, esi /* 0 */ 29 | cdq /* rdx=0 */ 30 | syscall 31 | EOS 32 | assert_equal(<<-'EOS', @shellcraft.sh(argv: true)) 33 | /* push argument array ["sh\x00"] */ 34 | /* push "sh\x00" */ 35 | push 0x1010101 ^ 0x6873 36 | xor dword ptr [rsp], 0x1010101 37 | xor esi, esi /* 0 */ 38 | push rsi /* null terminate */ 39 | push 8 40 | pop rsi 41 | add rsi, rsp 42 | push rsi /* "sh\x00" */ 43 | mov rsi, rsp 44 | 45 | /* push "/bin///sh\x00" */ 46 | push 0x68 47 | mov rax, 0x732f2f2f6e69622f 48 | push rax 49 | 50 | /* call execve("rsp", "rsi", 0) */ 51 | push 0x3b /* (SYS_execve) */ 52 | pop rax 53 | mov rdi, rsp 54 | cdq /* rdx=0 */ 55 | syscall 56 | EOS 57 | assert_equal(<<-'EOS', @shellcraft.sh(argv: ['sh', '-c', 'echo pusheen'])) 58 | /* push argument array ["sh\x00", "-c\x00", "echo pusheen\x00"] */ 59 | /* push "sh\x00-c\x00echo pusheen\x00" */ 60 | push 0x1010101 ^ 0x6e65 61 | xor dword ptr [rsp], 0x1010101 62 | mov rax, 0x6568737570206f68 63 | push rax 64 | mov rax, 0x101010101010101 65 | push rax 66 | mov rax, 0x626401622c016972 /* 0x101010101010101 ^ 0x636500632d006873 */ 67 | xor [rsp], rax 68 | xor esi, esi /* 0 */ 69 | push rsi /* null terminate */ 70 | push 0xe 71 | pop rsi 72 | add rsi, rsp 73 | push rsi /* "echo pusheen\x00" */ 74 | push 0x13 75 | pop rsi 76 | add rsi, rsp 77 | push rsi /* "-c\x00" */ 78 | push 0x18 79 | pop rsi 80 | add rsi, rsp 81 | push rsi /* "sh\x00" */ 82 | mov rsi, rsp 83 | 84 | /* push "/bin///sh\x00" */ 85 | push 0x68 86 | mov rax, 0x732f2f2f6e69622f 87 | push rax 88 | 89 | /* call execve("rsp", "rsi", 0) */ 90 | push 0x3b /* (SYS_execve) */ 91 | pop rax 92 | mov rdi, rsp 93 | cdq /* rdx=0 */ 94 | syscall 95 | EOS 96 | end 97 | end 98 | 99 | def test_i386 100 | context.local(arch: 'i386') do 101 | assert_equal(<<-'EOS', @shellcraft.sh) 102 | /* push "/bin///sh\x00" */ 103 | push 0x68 104 | push 0x732f2f2f 105 | push 0x6e69622f 106 | 107 | /* call execve("esp", 0, 0) */ 108 | push 0xb /* (SYS_execve) */ 109 | pop eax 110 | mov ebx, esp 111 | xor ecx, ecx /* 0 */ 112 | cdq /* edx=0 */ 113 | int 0x80 114 | EOS 115 | assert_equal(@shellcraft.execve('/bin///sh', ['sh'], 0), @shellcraft.sh(argv: true)) 116 | assert_equal(@shellcraft.execve('/bin///sh', ['sh', '-c', 'echo pusheen'], 0), 117 | @shellcraft.sh(argv: ['sh', '-c', 'echo pusheen'])) 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /test/shellcraft/linux/sleep_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'benchmark' 5 | 6 | require 'test_helper' 7 | 8 | require 'pwnlib/context' 9 | require 'pwnlib/runner' 10 | require 'pwnlib/shellcraft/shellcraft' 11 | 12 | class SleepTest < MiniTest::Test 13 | include ::Pwnlib::Context 14 | 15 | def setup 16 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 17 | end 18 | 19 | def test_amd64 20 | context.local(arch: :amd64) do 21 | assert_equal(<<-'EOS', @shellcraft.sleep(10)) 22 | /* push "\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" */ 23 | push 1 24 | dec byte ptr [rsp] 25 | push 0xb 26 | dec byte ptr [rsp] 27 | /* call nanosleep("rsp", 0) */ 28 | push 0x23 /* (SYS_nanosleep) */ 29 | pop rax 30 | mov rdi, rsp 31 | xor esi, esi /* 0 */ 32 | syscall 33 | add rsp, 16 /* recover rsp */ 34 | EOS 35 | end 36 | end 37 | 38 | def test_i386 39 | context.local(arch: :i386) do 40 | assert_equal(<<-'EOS', @shellcraft.sleep(1 + 3e-9)) 41 | /* push "\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00" */ 42 | push 1 43 | dec byte ptr [esp] 44 | push 3 45 | push 1 46 | dec byte ptr [esp] 47 | push 1 48 | /* call nanosleep("esp", 0) */ 49 | xor eax, eax 50 | mov al, 0xa2 /* (SYS_nanosleep) */ 51 | mov ebx, esp 52 | xor ecx, ecx /* 0 */ 53 | int 0x80 54 | add esp, 16 /* recover esp */ 55 | EOS 56 | end 57 | end 58 | 59 | def test_run 60 | linux_only 61 | 62 | context.local(arch: :amd64) do 63 | asm = @shellcraft.sleep(0.3) + @shellcraft.exit(0) 64 | t = Benchmark.realtime { ::Pwnlib::Runner.run_assembly(asm).recvall } 65 | assert_operator t, :>=, 0.3 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/shellcraft/linux/syscalls/execve_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class ExecveTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.execve('/bin///sh', 0, nil)) 19 | /* push "/bin///sh\x00" */ 20 | push 0x68 21 | mov rax, 0x732f2f2f6e69622f 22 | push rax 23 | 24 | /* call execve("rsp", 0, 0) */ 25 | push 0x3b /* (SYS_execve) */ 26 | pop rax 27 | mov rdi, rsp 28 | xor esi, esi /* 0 */ 29 | cdq /* rdx=0 */ 30 | syscall 31 | EOS 32 | assert_equal(<<-'EOS', @shellcraft.execve('rax', ['sh'], PWD: '.')) 33 | /* push argument array ["sh\x00"] */ 34 | /* push "sh\x00" */ 35 | push 0x1010101 ^ 0x6873 36 | xor dword ptr [rsp], 0x1010101 37 | xor esi, esi /* 0 */ 38 | push rsi /* null terminate */ 39 | push 8 40 | pop rsi 41 | add rsi, rsp 42 | push rsi /* "sh\x00" */ 43 | mov rsi, rsp 44 | 45 | /* push argument array ["PWD=.\x00"] */ 46 | /* push "PWD=.\x00" */ 47 | mov rax, 0x101010101010101 48 | push rax 49 | mov rax, 0x101012f3c455651 /* 0x101010101010101 ^ 0x2e3d445750 */ 50 | xor [rsp], rax 51 | xor edx, edx /* 0 */ 52 | push rdx /* null terminate */ 53 | push 8 54 | pop rdx 55 | add rdx, rsp 56 | push rdx /* "PWD=.\x00" */ 57 | mov rdx, rsp 58 | 59 | /* call execve("rax", "rsi", "rdx") */ 60 | mov rdi, rax 61 | push 0x3b /* (SYS_execve) */ 62 | pop rax 63 | syscall 64 | EOS 65 | assert_equal(<<-'EOS', @shellcraft.execve('rdi', 'rsi', 'rdx')) 66 | /* call execve("rdi", "rsi", "rdx") */ 67 | push 0x3b /* (SYS_execve) */ 68 | pop rax 69 | syscall 70 | EOS 71 | err = assert_raises(ArgumentError) { @shellcraft.execve('rdi', 'rdi', 'xdd') } 72 | assert_match(/not a valid register/, err.message) 73 | err = assert_raises(ArgumentError) { @shellcraft.execve('rdi', 'qqpie', 'rdi') } 74 | assert_match(/not a valid register/, err.message) 75 | end 76 | end 77 | 78 | def test_i386 79 | context.local(arch: 'i386') do 80 | assert_equal(<<-'EOS', @shellcraft.execve('/bin///sh', 0, nil)) 81 | /* push "/bin///sh\x00" */ 82 | push 0x68 83 | push 0x732f2f2f 84 | push 0x6e69622f 85 | 86 | /* call execve("esp", 0, 0) */ 87 | push 0xb /* (SYS_execve) */ 88 | pop eax 89 | mov ebx, esp 90 | xor ecx, ecx /* 0 */ 91 | cdq /* edx=0 */ 92 | int 0x80 93 | EOS 94 | assert_equal(<<-'EOS', @shellcraft.execve('eax', ['sh'], PWD: '.')) 95 | /* push argument array ["sh\x00"] */ 96 | /* push "sh\x00" */ 97 | push 0x1010101 98 | xor dword ptr [esp], 0x1016972 /* 0x1010101 ^ 0x6873 */ 99 | xor ecx, ecx /* 0 */ 100 | push ecx /* null terminate */ 101 | push 4 102 | pop ecx 103 | add ecx, esp 104 | push ecx /* "sh\x00" */ 105 | mov ecx, esp 106 | 107 | /* push argument array ["PWD=.\x00"] */ 108 | /* push "PWD=.\x00" */ 109 | push 0x2e 110 | push 0x3d445750 111 | xor edx, edx /* 0 */ 112 | push edx /* null terminate */ 113 | push 4 114 | pop edx 115 | add edx, esp 116 | push edx /* "PWD=.\x00" */ 117 | mov edx, esp 118 | 119 | /* call execve("eax", "ecx", "edx") */ 120 | mov ebx, eax 121 | push 0xb /* (SYS_execve) */ 122 | pop eax 123 | int 0x80 124 | EOS 125 | assert_equal(<<-'EOS', @shellcraft.execve('ebx', 'ecx', 'edx')) 126 | /* call execve("ebx", "ecx", "edx") */ 127 | push 0xb /* (SYS_execve) */ 128 | pop eax 129 | int 0x80 130 | EOS 131 | err = assert_raises(ArgumentError) { @shellcraft.execve('edi', 'esi', 'xdd') } 132 | assert_match(/not a valid register/, err.message) 133 | err = assert_raises(ArgumentError) { @shellcraft.execve('edi', 'qqpie', 'edi') } 134 | assert_match(/not a valid register/, err.message) 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /test/shellcraft/linux/syscalls/exit_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class ExitTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.exit) 19 | /* call exit(0) */ 20 | push 0x3c /* (SYS_exit) */ 21 | pop rax 22 | xor edi, edi /* 0 */ 23 | syscall 24 | EOS 25 | 26 | assert_equal(<<-'EOS', @shellcraft.exit(1)) 27 | /* call exit(1) */ 28 | push 0x3c /* (SYS_exit) */ 29 | pop rax 30 | push 1 31 | pop rdi 32 | syscall 33 | EOS 34 | end 35 | end 36 | 37 | def test_i386 38 | context.local(arch: 'i386') do 39 | assert_equal(<<-'EOS', @shellcraft.exit) 40 | /* call exit(0) */ 41 | push 1 /* (SYS_exit) */ 42 | pop eax 43 | xor ebx, ebx /* 0 */ 44 | int 0x80 45 | EOS 46 | 47 | assert_equal(<<-'EOS', @shellcraft.exit(1)) 48 | /* call exit(1) */ 49 | push 1 /* (SYS_exit) */ 50 | pop eax 51 | push 1 52 | pop ebx 53 | int 0x80 54 | EOS 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/shellcraft/linux/syscalls/open_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class OpenTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.open('/etc/passwd')) 19 | /* push "/etc/passwd\x00" */ 20 | push 0x1010101 ^ 0x647773 21 | xor dword ptr [rsp], 0x1010101 22 | mov rax, 0x7361702f6374652f 23 | push rax 24 | /* call open("rsp", "O_RDONLY", 0) */ 25 | push 2 /* (SYS_open) */ 26 | pop rax 27 | mov rdi, rsp 28 | xor esi, esi /* (O_RDONLY) */ 29 | cdq /* rdx=0 */ 30 | syscall 31 | EOS 32 | 33 | assert_equal(<<-'EOS', @shellcraft.open('/etc/passwd', 0x40, 0o750)) # O_CREAT = 0x40 34 | /* push "/etc/passwd\x00" */ 35 | push 0x1010101 ^ 0x647773 36 | xor dword ptr [rsp], 0x1010101 37 | mov rax, 0x7361702f6374652f 38 | push rax 39 | /* call open("rsp", 64, 488) */ 40 | push 2 /* (SYS_open) */ 41 | pop rax 42 | mov rdi, rsp 43 | push 0x40 44 | pop rsi 45 | xor edx, edx 46 | mov dx, 0x1e8 47 | syscall 48 | EOS 49 | end 50 | end 51 | 52 | def test_i386 53 | context.local(arch: 'i386') do 54 | assert_equal(<<-'EOS', @shellcraft.open('/etc/passwd')) 55 | /* push "/etc/passwd\x00" */ 56 | push 0x1010101 57 | xor dword ptr [esp], 0x1657672 /* 0x1010101 ^ 0x647773 */ 58 | push 0x7361702f 59 | push 0x6374652f 60 | /* call open("esp", "O_RDONLY", 0) */ 61 | push 5 /* (SYS_open) */ 62 | pop eax 63 | mov ebx, esp 64 | xor ecx, ecx /* (O_RDONLY) */ 65 | cdq /* edx=0 */ 66 | int 0x80 67 | EOS 68 | 69 | assert_equal(<<-'EOS', @shellcraft.open('/etc/passwd', 'O_CREAT', 0o750)) 70 | /* push "/etc/passwd\x00" */ 71 | push 0x1010101 72 | xor dword ptr [esp], 0x1657672 /* 0x1010101 ^ 0x647773 */ 73 | push 0x7361702f 74 | push 0x6374652f 75 | /* call open("esp", "O_CREAT", 488) */ 76 | push 5 /* (SYS_open) */ 77 | pop eax 78 | mov ebx, esp 79 | push 0x40 /* (O_CREAT) */ 80 | pop ecx 81 | xor edx, edx 82 | mov dx, 0x1e8 83 | int 0x80 84 | EOS 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/shellcraft/linux/syscalls/syscall_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class SyscallTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.syscall('SYS_execve', 1, 'rsp', 2, 0)) 19 | /* call execve(1, "rsp", 2, 0) */ 20 | push 0x3b /* (SYS_execve) */ 21 | pop rax 22 | push 1 23 | pop rdi 24 | mov rsi, rsp 25 | push 2 26 | pop rdx 27 | xor r10d, r10d /* 0 */ 28 | syscall 29 | EOS 30 | assert_equal(<<-'EOS', @shellcraft.syscall) 31 | /* call syscall() */ 32 | syscall 33 | EOS 34 | assert_equal(<<-'EOS', @shellcraft.syscall('rax', 'rdi', 'rsi')) 35 | /* call syscall("rax", "rdi", "rsi") */ 36 | /* setregs noop */ 37 | syscall 38 | EOS 39 | assert_equal(<<-'EOS', @shellcraft.syscall('rbp', nil, nil, 1)) 40 | /* call syscall("rbp", ?, ?, 1) */ 41 | mov rax, rbp 42 | push 1 43 | pop rdx 44 | syscall 45 | EOS 46 | mmap = @shellcraft.syscall('SYS_mmap', 0, 4096, 47 | 'PROT_READ | PROT_WRITE | PROT_EXEC', 48 | 'MAP_PRIVATE | MAP_ANONYMOUS', -1, 0) 49 | assert_equal(<<-'EOS', mmap) 50 | /* call mmap(0, 4096, "PROT_READ | PROT_WRITE | PROT_EXEC", "MAP_PRIVATE | MAP_ANONYMOUS", -1, 0) */ 51 | push 9 /* (SYS_mmap) */ 52 | pop rax 53 | xor edi, edi /* 0 */ 54 | mov esi, 0x1010101 55 | xor esi, 0x1011101 /* 0x1000 == 0x1010101 ^ 0x1011101 */ 56 | push 7 /* (PROT_READ | PROT_WRITE | PROT_EXEC) */ 57 | pop rdx 58 | push 0x22 /* (MAP_PRIVATE | MAP_ANONYMOUS) */ 59 | pop r10 60 | push -1 61 | pop r8 62 | xor r9d, r9d /* 0 */ 63 | syscall 64 | EOS 65 | end 66 | end 67 | 68 | def test_i386 69 | context.local(arch: 'i386') do 70 | assert_equal(<<-'EOS', @shellcraft.syscall('ebp', nil, nil, 1)) 71 | /* call syscall("ebp", ?, ?, 1) */ 72 | mov eax, ebp 73 | push 1 74 | pop edx 75 | int 0x80 76 | EOS 77 | assert_equal(<<-'EOS', @shellcraft.syscall('eax', 'ebx', 'ecx')) 78 | /* call syscall("eax", "ebx", "ecx") */ 79 | /* setregs noop */ 80 | int 0x80 81 | EOS 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/shellcraft/memcpy_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class MemcpyTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.memcpy('rdi', 'rbx', 255)) 19 | /* memcpy("rdi", "rbx", 0xff) */ 20 | cld 21 | mov rsi, rbx 22 | xor ecx, ecx 23 | mov cl, 0xff 24 | rep movsb 25 | EOS 26 | assert_equal(<<-'EOS', @shellcraft.memcpy('rdi', 0x602020, 10)) 27 | /* memcpy("rdi", 0x602020, 0xa) */ 28 | cld 29 | mov esi, 0x1010101 30 | xor esi, 0x1612121 /* 0x602020 == 0x1010101 ^ 0x1612121 */ 31 | push 9 /* mov ecx, '\n' */ 32 | pop rcx 33 | inc ecx 34 | rep movsb 35 | EOS 36 | end 37 | end 38 | 39 | def test_i386 40 | context.local(arch: :i386) do 41 | assert_equal(<<-'EOS', @shellcraft.memcpy('eax', 'ebx', 'ecx')) 42 | /* memcpy("eax", "ebx", "ecx") */ 43 | cld 44 | mov edi, eax 45 | mov esi, ebx 46 | rep movsb 47 | EOS 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/shellcraft/nop_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class NopTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(" nop\n", @shellcraft.nop) 19 | end 20 | end 21 | 22 | def test_i386 23 | context.local(arch: 'i386') do 24 | assert_equal(" nop\n", @shellcraft.nop) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/shellcraft/popad_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class PopadTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.popad) 19 | pop rdi 20 | pop rsi 21 | pop rbp 22 | pop rbx /* add rsp, 8 */ 23 | pop rbx 24 | pop rdx 25 | pop rcx 26 | pop rax 27 | EOS 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/shellcraft/pushstr_array_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class PushstrArrayTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.pushstr_array('rcx', ['A'])) 19 | /* push argument array ["A\x00"] */ 20 | /* push "A\x00" */ 21 | push 0x41 22 | xor ecx, ecx /* 0 */ 23 | push rcx /* null terminate */ 24 | push 8 25 | pop rcx 26 | add rcx, rsp 27 | push rcx /* "A\x00" */ 28 | mov rcx, rsp 29 | EOS 30 | assert_equal(<<-'EOS', @shellcraft.pushstr_array('rsp', ['sh', '-c', 'echo pusheen'])) 31 | /* push argument array ["sh\x00", "-c\x00", "echo pusheen\x00"] */ 32 | /* push "sh\x00-c\x00echo pusheen\x00" */ 33 | push 0x1010101 ^ 0x6e65 34 | xor dword ptr [rsp], 0x1010101 35 | mov rax, 0x6568737570206f68 36 | push rax 37 | mov rax, 0x101010101010101 38 | push rax 39 | mov rax, 0x626401622c016972 /* 0x101010101010101 ^ 0x636500632d006873 */ 40 | xor [rsp], rax 41 | xor esp, esp /* 0 */ 42 | push rsp /* null terminate */ 43 | push 0xe 44 | pop rsp 45 | add rsp, rsp 46 | push rsp /* "echo pusheen\x00" */ 47 | push 0x13 48 | pop rsp 49 | add rsp, rsp 50 | push rsp /* "-c\x00" */ 51 | push 0x18 52 | pop rsp 53 | add rsp, rsp 54 | push rsp /* "sh\x00" */ 55 | /* moving rsp into rsp, but this is a no-op */ 56 | EOS 57 | end 58 | end 59 | 60 | def test_i386 61 | context.local(arch: 'i386') do 62 | assert_equal(<<-'EOS', @shellcraft.pushstr_array('esp', ['sh', '-c', 'echo pusheen'])) 63 | /* push argument array ["sh\x00", "-c\x00", "echo pusheen\x00"] */ 64 | /* push "sh\x00-c\x00echo pusheen\x00" */ 65 | push 0x1010101 66 | xor dword ptr [esp], 0x1016f64 /* 0x1010101 ^ 0x6e65 */ 67 | push 0x65687375 68 | push 0x70206f68 69 | push 0x1010101 70 | xor dword ptr [esp], 0x62640162 /* 0x1010101 ^ 0x63650063 */ 71 | push 0x1010101 72 | xor dword ptr [esp], 0x2c016972 /* 0x1010101 ^ 0x2d006873 */ 73 | xor esp, esp /* 0 */ 74 | push esp /* null terminate */ 75 | push 9 /* mov esp, '\n' */ 76 | pop esp 77 | inc esp 78 | add esp, esp 79 | push esp /* "echo pusheen\x00" */ 80 | push 0xb 81 | pop esp 82 | add esp, esp 83 | push esp /* "-c\x00" */ 84 | push 0xc 85 | pop esp 86 | add esp, esp 87 | push esp /* "sh\x00" */ 88 | /* moving esp into esp, but this is a no-op */ 89 | EOS 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /test/shellcraft/pushstr_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class PushstrTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.pushstr('A')) 19 | /* push "A\x00" */ 20 | push 0x41 21 | EOS 22 | assert_equal(<<-'EOS', @shellcraft.pushstr("\n")) 23 | /* push "\n\x00" */ 24 | push 0xb 25 | dec byte ptr [rsp] 26 | EOS 27 | assert_equal(<<-'EOS', @shellcraft.pushstr('A' * 4)) 28 | /* push "AAAA\x00" */ 29 | push 0x41414141 30 | EOS 31 | assert_equal(<<-'EOS', @shellcraft.pushstr('A' * 8)) 32 | /* push "AAAAAAAA\x00" */ 33 | push 1 34 | dec byte ptr [rsp] 35 | mov rax, 0x4141414141414141 36 | push rax 37 | EOS 38 | assert_equal(<<-'EOS', @shellcraft.pushstr('A' * 8, append_null: false)) 39 | /* push "AAAAAAAA" */ 40 | mov rax, 0x4141414141414141 41 | push rax 42 | EOS 43 | assert_equal(<<-'EOS', @shellcraft.pushstr("\n" * 4)) 44 | /* push "\n\n\n\n\x00" */ 45 | push 0x1010101 ^ 0xa0a0a0a 46 | xor dword ptr [rsp], 0x1010101 47 | EOS 48 | assert_equal(<<-'EOS', @shellcraft.pushstr('/bin/sh')) 49 | /* push "/bin/sh\x00" */ 50 | mov rax, 0x101010101010101 51 | push rax 52 | mov rax, 0x169722e6f68632e /* 0x101010101010101 ^ 0x68732f6e69622f */ 53 | xor [rsp], rax 54 | EOS 55 | assert_equal(<<-'EOS', @shellcraft.pushstr("\x00\xff\xff\xff\xff\xff\xff\xff", append_null: false)) 56 | /* push "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF" */ 57 | mov rax, 0x101010101010101 58 | push rax 59 | mov rax, -0x1010101010101ff /* 0x101010101010101 ^ -0x100 */ 60 | xor [rsp], rax 61 | EOS 62 | end 63 | end 64 | 65 | def test_i386 66 | context.local(arch: 'i386') do 67 | assert_equal(<<-'EOS', @shellcraft.pushstr('A')) 68 | /* push "A\x00" */ 69 | push 0x41 70 | EOS 71 | assert_equal(<<-'EOS', @shellcraft.pushstr("\n")) 72 | /* push "\n\x00" */ 73 | push 0xb 74 | dec byte ptr [esp] 75 | EOS 76 | assert_equal(<<-'EOS', @shellcraft.pushstr('A' * 4)) 77 | /* push "AAAA\x00" */ 78 | push 1 79 | dec byte ptr [esp] 80 | push 0x41414141 81 | EOS 82 | assert_equal(<<-'EOS', @shellcraft.pushstr('A' * 8)) 83 | /* push "AAAAAAAA\x00" */ 84 | push 1 85 | dec byte ptr [esp] 86 | push 0x41414141 87 | push 0x41414141 88 | EOS 89 | assert_equal(<<-'EOS', @shellcraft.pushstr('A' * 8, append_null: false)) 90 | /* push "AAAAAAAA" */ 91 | push 0x41414141 92 | push 0x41414141 93 | EOS 94 | assert_equal(<<-'EOS', @shellcraft.pushstr("\n" * 4)) 95 | /* push "\n\n\n\n\x00" */ 96 | push 1 97 | dec byte ptr [esp] 98 | push 0x1010101 99 | xor dword ptr [esp], 0xb0b0b0b /* 0x1010101 ^ 0xa0a0a0a */ 100 | EOS 101 | assert_equal(<<-'EOS', @shellcraft.pushstr('/bin/sh')) 102 | /* push "/bin/sh\x00" */ 103 | push 0x1010101 104 | xor dword ptr [esp], 0x169722e /* 0x1010101 ^ 0x68732f */ 105 | push 0x6e69622f 106 | EOS 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/shellcraft/registers_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/shellcraft/registers' 7 | 8 | class RegistersTest < MiniTest::Test 9 | include ::Pwnlib::Shellcraft::Registers 10 | 11 | def test_get_register 12 | assert_instance_of(::Pwnlib::Shellcraft::Registers::Register, get_register('rdi')) 13 | assert_nil(get_register('meow')) 14 | end 15 | 16 | def test_regtsiter? 17 | assert_equal(true, register?(:ax)) 18 | assert_equal(true, register?('ax')) 19 | assert_equal(true, register?('r8')) 20 | assert_equal(true, register?('r15b')) 21 | assert_equal(true, register?('r15w')) 22 | assert_equal(false, register?('xdd')) 23 | assert_equal(false, register?('')) 24 | end 25 | 26 | def test_register 27 | assert_equal(8, get_register('rdi').bytes) 28 | assert_equal(16, get_register('ax').bits) 29 | assert_equal('r10d', get_register('r10d').name) 30 | assert_equal('r10d', get_register('r10d').to_s) 31 | assert_equal('Register(r10d)', get_register('r10d').inspect) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/shellcraft/ret_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class RetTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(" ret\n", @shellcraft.ret) 19 | assert_equal(" mov rax, rdi\n ret\n", @shellcraft.ret(:rdi)) 20 | assert_equal(" xor eax, eax /* 0 */\n ret\n", @shellcraft.ret(0)) 21 | assert_equal(<<-'EOS', @shellcraft.ret(0x100000000)) 22 | mov rax, 0x101010201010101 23 | push rax 24 | mov rax, 0x101010301010101 25 | xor [rsp], rax /* 0x100000000 == 0x101010201010101 ^ 0x101010301010101 */ 26 | pop rax 27 | ret 28 | EOS 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/shellcraft/setregs_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class SetregsTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_amd64 17 | context.local(arch: 'amd64') do 18 | assert_equal(<<-'EOS', @shellcraft.setregs({ rax: 1, rbx: 'rax' })) 19 | mov rbx, rax 20 | push 1 21 | pop rax 22 | EOS 23 | assert_equal(<<-'EOS', @shellcraft.setregs({ rax: 'SYS_write', rbx: 'rax' })) 24 | mov rbx, rax 25 | push 1 /* (SYS_write) */ 26 | pop rax 27 | EOS 28 | assert_equal(<<-'EOS', @shellcraft.setregs({ rax: 'rbx', rbx: 'rax', rcx: 'rbx' })) 29 | mov rcx, rbx 30 | xchg rax, rbx 31 | EOS 32 | assert_equal(<<-'EOS', @shellcraft.setregs({ rax: 1, rdx: 0 })) 33 | push 1 34 | pop rax 35 | cdq /* rdx=0 */ 36 | EOS 37 | # issue #50 38 | assert_equal(<<-'EOS', @shellcraft.setregs({ rdi: :rax, rsi: :rdi })) 39 | mov rsi, rdi 40 | mov rdi, rax 41 | EOS 42 | end 43 | end 44 | 45 | def test_i386 46 | context.local(arch: 'i386') do 47 | assert_equal(<<-EOS, @shellcraft.setregs({ eax: 1, ebx: 'eax' })) 48 | mov ebx, eax 49 | push 1 50 | pop eax 51 | EOS 52 | assert_equal(<<-EOS, @shellcraft.setregs({ eax: 'ebx', ebx: 'eax', ecx: 'ebx' })) 53 | mov ecx, ebx 54 | xchg eax, ebx 55 | EOS 56 | assert_equal(<<-'EOS', @shellcraft.setregs({ eax: 1, edx: 0 })) 57 | push 1 58 | pop eax 59 | cdq /* edx=0 */ 60 | EOS 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/shellcraft/shellcraft_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/shellcraft/shellcraft' 8 | 9 | class ShellcraftTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | 12 | def setup 13 | @shellcraft = ::Pwnlib::Shellcraft::Shellcraft.instance 14 | end 15 | 16 | def test_respond 17 | context.local(arch: 'amd64') do 18 | # Check respond_to_missing? is well defined 19 | assert(@shellcraft.respond_to?(:mov)) 20 | assert(@shellcraft.method(:sh)) 21 | end 22 | refute(@shellcraft.respond_to?(:linux)) 23 | assert_raises(NoMethodError) { @shellcraft.meow } 24 | 25 | context.local(arch: 'arm') do 26 | err = assert_raises(::Pwnlib::Errors::UnsupportedArchError) { @shellcraft.respond_to?(:mov) } 27 | assert_equal("Can't use shellcraft under architecture \"arm\".", err.message) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rainbow' 4 | require 'simplecov' 5 | require 'tty/platform' 6 | 7 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( 8 | [SimpleCov::Formatter::HTMLFormatter] 9 | ) 10 | SimpleCov.start do 11 | add_filter '/test/' 12 | end 13 | 14 | require 'minitest/autorun' 15 | require 'minitest/unit' 16 | require 'minitest/hell' 17 | 18 | module MiniTest 19 | class Test 20 | def before_setup 21 | super 22 | # Default to disable coloring for easier testing. 23 | Rainbow.enabled = false 24 | end 25 | 26 | def linux_only(msg = 'Only tested on Linux') 27 | skip msg unless TTY::Platform.new.linux? 28 | end 29 | 30 | def skip_windows(msg = 'Skip on Windows') 31 | skip msg if TTY::Platform.new.windows? 32 | end 33 | 34 | # Methods for hooking logger, 35 | # require 'pwnlib/logger' before using these methods. 36 | 37 | def log_null(&block) 38 | File.open(File::NULL, 'w') { |f| log_hook(f, &block) } 39 | end 40 | 41 | def log_stdout(&block) 42 | log_hook($stdout, &block) 43 | end 44 | 45 | def log_hook(obj) 46 | old = ::Pwnlib::Logger.log.instance_variable_get(:@logdev) 47 | ::Pwnlib::Logger.log.instance_variable_set(:@logdev, obj) 48 | begin 49 | yield 50 | ensure 51 | ::Pwnlib::Logger.log.instance_variable_set(:@logdev, old) 52 | end 53 | end 54 | 55 | # Hooks +$stdin+. 56 | # 57 | # @param [IO] io 58 | # IO object to be used as +$stdin+. 59 | # 60 | # @return [Object] 61 | # Returns what the block returned. 62 | def hook_stdin(io) 63 | org_stdin = $stdin 64 | $stdin = io 65 | begin 66 | yield 67 | ensure 68 | $stdin = org_stdin 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/timer_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/timer' 7 | 8 | class TimerTest < MiniTest::Test 9 | include ::Pwnlib 10 | 11 | def test_countdown 12 | t = Timer.new 13 | refute(t.started?) 14 | refute(t.active?) 15 | assert_equal('DARKHH QQ', t.countdown(0.1) { 'DARKHH QQ' }) 16 | 17 | exception = assert_raises(RuntimeError) { t.countdown(0.1) { t.countdown(0.1) {} } } 18 | assert_equal('Nested countdown not permitted', exception.message) 19 | 20 | t.timeout = 0.514 21 | exception = assert_raises(RuntimeError) do 22 | t.countdown(0.1) { t.timeout = :forever } 23 | end 24 | assert_equal("Can't change timeout when countdown", exception.message) 25 | 26 | t.countdown(0.1) { assert(t.started?) } 27 | t.countdown(0.1) { assert(t.active?) } 28 | 29 | assert_raises(::Pwnlib::Errors::TimeoutError) do 30 | t.countdown(0.1) { sleep(0.2) } 31 | end 32 | end 33 | 34 | # Check #115 fixed 35 | def test_nested_countdown_false_positve_115 36 | t = Timer.new 37 | assert_raises(::Pwnlib::Errors::TimeoutError) do 38 | t.countdown(0.01) { sleep(1) } 39 | end 40 | t.countdown(0.01) {} 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/tubes/buffer_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/tubes/buffer' 7 | 8 | class BufferTest < MiniTest::Test 9 | def test_add 10 | b = ::Pwnlib::Tubes::Buffer.new 11 | b.add('A' * 10) 12 | b.add('B' * 10) 13 | assert_equal(20, b.size) 14 | assert_equal('A', b.get(1)) 15 | assert_equal(19, b.size) 16 | assert_equal(false, b.empty?) 17 | assert_equal('AAAAAAAAABBBBBBBBBB', b.get(9999)) 18 | assert_equal(0, b.size) 19 | assert_equal(true, b.empty?) 20 | assert_equal('', b.get(1)) 21 | end 22 | 23 | def test_unget 24 | b = ::Pwnlib::Tubes::Buffer.new 25 | b.add('hello') 26 | b.add('world') 27 | assert_equal('hello', b.get(5)) 28 | b.unget('goodbye') 29 | assert_equal('goodbyeworld', b.get) 30 | end 31 | 32 | def test_buffer 33 | b = ::Pwnlib::Tubes::Buffer.new 34 | b2 = ::Pwnlib::Tubes::Buffer.new 35 | b.add('hello') 36 | b2.add('world') 37 | b.add(b2) 38 | assert_equal('hello', b.get(5)) 39 | assert_equal(false, b2.empty?) 40 | assert_equal('world', b2.get) 41 | b2.add('goodbye') 42 | b.unget(b2) 43 | assert_equal('goodbyeworld', b.get) 44 | assert_equal('goodbye', b2.get) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/tubes/process_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'socket' 5 | require 'tty-platform' 6 | 7 | require 'test_helper' 8 | 9 | require 'pwnlib/context' 10 | require 'pwnlib/errors' 11 | require 'pwnlib/tubes/process' 12 | 13 | class ProcessTest < MiniTest::Test 14 | include ::Pwnlib::Context 15 | 16 | def setup 17 | skip_windows 18 | end 19 | 20 | def test_io 21 | cat = ::Pwnlib::Tubes::Process.new('cat') 22 | cat.puts('HAHA') 23 | assert_equal("HAHA\n", cat.gets) 24 | assert_raises(::Pwnlib::Errors::TimeoutError) { cat.gets(timeout: 0.1) } 25 | cat.puts('HAHA2') 26 | assert_equal("HAHA2\n", cat.gets) 27 | end 28 | 29 | def test_env 30 | data = ::Pwnlib::Tubes::Process.new('env').read 31 | assert_match('PATH=', data) 32 | data = ::Pwnlib::Tubes::Process.new('env', env: { 'FOO' => 'BAR' }).read 33 | assert_equal("FOO=BAR\n", data) 34 | end 35 | 36 | def test_aslr 37 | linux_only 38 | 39 | map1 = ::Pwnlib::Tubes::Process.new('cat /proc/self/maps', aslr: false).read 40 | map2 = ::Pwnlib::Tubes::Process.new(['cat', '/proc/self/maps'], aslr: false).read 41 | assert_match('/bin/cat', map1) # make sure it read something 42 | assert_equal(map1, map2) 43 | end 44 | 45 | def test_eof 46 | ls = ::Pwnlib::Tubes::Process.new(['ls', '-la']) 47 | assert_match(/total/, ls.gets) 48 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { loop { ls.write('anything') } } 49 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { loop { ls.gets } } 50 | end 51 | 52 | def test_shutdown 53 | cat = ::Pwnlib::Tubes::Process.new('cat') 54 | assert_raises(::Pwnlib::Errors::TimeoutError) { cat.recvn(1, timeout: 0.1) } 55 | cat.shutdown(:write) # This should cause `cat` dead 56 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { cat.recvn(1, timeout: 0.1) } 57 | cat.shutdown 58 | 59 | cat = ::Pwnlib::Tubes::Process.new('cat') 60 | cat.shutdown(:read) 61 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { cat.recvn(1) } 62 | cat.shutdown 63 | assert_raises(ArgumentError) { cat.shutdown(:zz) } 64 | end 65 | 66 | def test_kill 67 | cat = ::Pwnlib::Tubes::Process.new('cat') 68 | cat.kill 69 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { cat.recvn(1) } 70 | end 71 | 72 | def test_tty 73 | tty_test = proc do |*args, raw: true| 74 | in_, out = args.map { |v| v ? :pty : :pipe } 75 | process = ::Pwnlib::Tubes::Process.new('ruby -e "p [STDIN.tty?, STDOUT.tty?]"', 76 | in: in_, out: out, raw: raw) 77 | process.gets 78 | end 79 | assert_equal("[false, false]\n", tty_test.call(false, false)) 80 | assert_equal("[true, false]\n", tty_test.call(true, false)) 81 | assert_equal("[false, true]\n", tty_test.call(false, true)) 82 | assert_equal("[false, true]\r\n", tty_test.call(false, true, raw: false)) 83 | 84 | cat = ::Pwnlib::Tubes::Process.new('cat', in: :pty, out: :pty, raw: false) 85 | cat.puts('Hi') 86 | # In cooked mode, tty should echo the input, so we can gets twice. 87 | assert_equal("Hi\r\n", cat.gets) 88 | assert_equal("Hi\r\n", cat.gets) 89 | class << cat 90 | # hook shutdown to silence the +cat: -: Input/output error+ message. 91 | def shutdown; end 92 | end 93 | cat.close 94 | end 95 | 96 | def test_interact 97 | ls = ::Pwnlib::Tubes::Process.new('ls Gemfile*') 98 | saved = $stdin 99 | # prevents stdin being closed 100 | $stdin = UDPSocket.new 101 | assert_output("Gemfile\nGemfile.lock\n") { context.local(log_level: :fatal) { ls.interact } } 102 | $stdin.close 103 | $stdin = saved 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /test/tubes/sock_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'open3' 5 | 6 | require 'test_helper' 7 | 8 | require 'pwnlib/tubes/sock' 9 | 10 | class SockTest < MiniTest::Test 11 | include ::Pwnlib::Tubes 12 | ECHO_FILE = File.expand_path('../data/echo.rb', __dir__) 13 | 14 | def popen_echo(data) 15 | Open3.popen2("ruby #{ECHO_FILE}") do |_i, o, _t| 16 | port = o.gets.split.last.to_i 17 | s = Sock.new('127.0.0.1', port) 18 | yield s, data, o 19 | end 20 | end 21 | 22 | def test_sock 23 | popen_echo('DARKHH') do |s, data, _o| 24 | s.sock.puts(data) 25 | rs, = IO.select([s.sock]) 26 | refute_nil(rs) 27 | assert_equal(data, s.sock.readpartial(data.size)) 28 | end 29 | end 30 | 31 | def test_eof 32 | popen_echo('DARKHH') do |s, data, o| 33 | s.puts(data) 34 | assert_equal("#{data}\n", s.gets) 35 | o.gets 36 | s.puts(514) 37 | sleep(0.1) # Wait for connection reset 38 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.puts(514) } 39 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.puts(514) } 40 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.recv } 41 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.recv } 42 | end 43 | end 44 | 45 | def test_close 46 | popen_echo('DARKHH') do |s, _data, _o| 47 | s.close 48 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.puts(514) } 49 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.puts(514) } 50 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.recv } 51 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.recv } 52 | assert_raises(ArgumentError) { s.close(:hh) } 53 | end 54 | 55 | popen_echo('DARKHH') do |s, _data, _o| 56 | 3.times { s.close(:read) } 57 | 3.times { s.close(:recv) } 58 | 3.times { s.close(:send) } 59 | 3.times { s.close(:write) } 60 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.puts(514) } 61 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.puts(514) } 62 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.recv } 63 | assert_raises(::Pwnlib::Errors::EndOfTubeError) { s.recv } 64 | 3.times { s.close } 65 | assert_raises(ArgumentError) { s.close(:shik) } 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/ui_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'stringio' 5 | 6 | require 'test_helper' 7 | 8 | require 'pwnlib/ui' 9 | 10 | class UITest < MiniTest::Test 11 | def test_pause 12 | hook_stdin(StringIO.new("\n")) do 13 | assert_output(<<-EOS) { log_stdout { ::Pwnlib::UI.pause } } 14 | [INFO] Paused (press enter to continue) 15 | EOS 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/util/cyclic_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/util/cyclic' 7 | 8 | class CyclicTest < MiniTest::Test 9 | include ::Pwnlib::Util::Cyclic 10 | 11 | def test_cyclic 12 | assert_equal('AAABAACABBABCACBACCBBBCBCCC', cyclic(alphabet: 'ABC', n: 3)) 13 | assert_equal('aaaabaaacaaadaaaeaaa', cyclic(20)) 14 | assert_equal(27_000, cyclic(alphabet: (0...30).to_a, n: 3).size) 15 | assert_equal([1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 2, 2, 2, 2], 16 | cyclic(alphabet: [1, 2], n: 4)) 17 | end 18 | 19 | def test_cyclic_find 20 | r = cyclic(1000) 21 | 10.times do 22 | idx = rand(0...1000 - 4) 23 | assert_equal(idx, cyclic_find(r[idx, 4])) 24 | end 25 | 26 | r = cyclic(1000) 27 | 10.times do 28 | idx = rand(0...1000 - 5) 29 | assert_equal(idx, cyclic_find(r[idx, 5], n: 4)) 30 | end 31 | 32 | r = cyclic(1000, alphabet: (0...10).to_a) 33 | 10.times do 34 | idx = rand(0...1000 - 4) 35 | assert_equal(idx, cyclic_find(r[idx, 4], alphabet: (0...10).to_a)) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/util/fiddling_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/util/fiddling' 7 | 8 | class FiddlingTest < MiniTest::Test 9 | include ::Pwnlib::Util::Fiddling 10 | 11 | def test_enhex 12 | assert_equal('4141313233', enhex('AA123')) 13 | assert_equal('74657374', enhex('test')) 14 | assert_equal('122355ff4499', enhex("\x12\x23\x55\xff\x44\x99")) 15 | assert_equal('21402324255e262a28295f2b5f29282a265e2524234040213f7b7d5d5b2f2f5c60607e', 16 | enhex('!@#$%^&*()_+_)(*&^%$#@@!?{}][//\``~')) 17 | end 18 | 19 | def test_unhex 20 | assert_equal('AA123', unhex('4141313233')) 21 | assert_equal('test', unhex('74657374')) 22 | assert_equal("\x12\x23\x55\xff\x44\x99", unhex('122355ff4499')) 23 | assert_equal('!@#$%^&*()_+_)(*&^%$#@@!?{}][//\``~', 24 | unhex('21402324255e262a28295f2b5f29282a265e2524234040213f7b7d5d5b2f2f5c60607e')) 25 | end 26 | 27 | def test_hex 28 | assert_equal('0x0', hex(0)) 29 | assert_equal('0x64', hex(100)) 30 | assert_equal('-0xa', hex(-10)) 31 | assert_equal('0xfaceb00cdeadbeef', hex(0xfaceb00cdeadbeef)) 32 | end 33 | 34 | def test_urlencode 35 | assert_equal('%74%65%73%74%20%41', urlencode('test A')) 36 | assert_equal('%00%ff%01%fe', urlencode("\x00\xff\x01\xfe")) 37 | end 38 | 39 | def test_urldecode 40 | assert_equal('test A', urldecode('te%73t%20%41')) 41 | assert_equal("\x00\xff\x01\xfe", urldecode('%00%ff%01%fe')) 42 | 43 | assert_equal('%qq', urldecode('%qq', ignore_invalid: true)) 44 | err = assert_raises(ArgumentError) { urldecode('%qq') } 45 | assert_match(/Invalid input to urldecode/, err.message) 46 | 47 | assert_equal('%%1z2%orz%%%%%#$!#)@%', urldecode('%%1z2%orz%%%%%#$!#)@%', ignore_invalid: true)) 48 | err = assert_raises(ArgumentError) { urldecode('%ff%') } 49 | assert_match(/Invalid input to urldecode/, err.message) 50 | end 51 | 52 | def test_bits 53 | assert_equal(['+', '+', '+', '+', '+', '+', '+', '-', '-', '-', '-', '-', '-', '-', '-', '-'], 54 | bits(511, zero: '+', one: '-')) 55 | assert_equal([0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0], bits('XD')) 56 | assert_equal([0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0], 57 | bits('XD', endian: 'le')) 58 | assert_equal([0, 0, 0, 0, 0, 0, 0, 0], bits(0)) 59 | 60 | err = assert_raises(ArgumentError) { bits(2.0) } 61 | assert_match(/must be either String or Integer/, err.message) 62 | 63 | err = assert_raises(ArgumentError) { bits(-1) } 64 | assert_match(/must be non-negative/, err.message) 65 | end 66 | 67 | def test_bits_str 68 | assert_equal('0000000111111111', bits_str(511)) 69 | assert_equal('0100011010010110001011101100111011111010110011100010111001001110', 70 | bits_str('bits_str', endian: 'little')) 71 | end 72 | 73 | def test_unbits 74 | assert_equal("\x80", unbits([1])) 75 | assert_equal("\x01", unbits([1], endian: 'le')) 76 | assert_equal("\x16\xa666\xf6", unbits(bits('hello'), endian: 'le')) 77 | 78 | err = assert_raises(ArgumentError) { unbits(%w(hi there)) } 79 | assert_match(/cannot decode value/, err.message) 80 | 81 | assert_equal("\xf0", unbits('11110000')) 82 | end 83 | 84 | def test_bitswap 85 | assert_equal("\x8cL\xcc,", bitswap('1234')) 86 | end 87 | 88 | def test_bitswap_int 89 | assert_equal(0x2c, bitswap_int(0x1234, bits: 8)) 90 | assert_equal(0x2c48, bitswap_int(0x1234, bits: 16)) 91 | assert_equal(0x2c4800, bitswap_int(0x1234, bits: 24)) 92 | assert_equal(0x589000, bitswap_int(0x1234, bits: 25)) 93 | 94 | context.local(bits: 36) do 95 | assert_equal(0xf77db57b0, bitswap_int(0xdeadbeef)) 96 | end 97 | end 98 | 99 | def test_b64e 100 | assert_equal('dGVzdA==', b64e('test')) 101 | assert_equal('shik' * 100, b64e("\xb2\x18\xa4" * 100)) 102 | end 103 | 104 | def test_b64d 105 | assert_equal('test', b64d('dGVzdA==')) 106 | assert_equal("\xb2\x18\xa4" * 100, b64d('shik' * 100)) 107 | end 108 | 109 | def test_xor 110 | assert_equal('happy', xor("\xE8\xE1\xF0\xF0\xF9", "\x80")) 111 | assert_equal('happy', xor("\x80", "\xE8\xE1\xF0\xF0\xF9")) 112 | assert_equal("\x04\x04\x04\x02\v\r\x11\x10\x11", xor('plaintext', 'thekey')) 113 | assert_equal('2172172172', xor('217', "\x00" * 10)) 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /test/util/getdents_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/context' 7 | require 'pwnlib/util/getdents' 8 | 9 | class GetdentsTest < MiniTest::Test 10 | include ::Pwnlib::Context 11 | include ::Pwnlib::Util::Getdents 12 | 13 | def test_parse 14 | context.local(arch: 'i386') do 15 | assert_equal("REG README.md\nDIR lib\n", 16 | parse("\x92\x22\x0e\x01\x8f\x4a\xb3\x41" \ 17 | "\x18\x00\x52\x45\x41\x44\x4d\x45" \ 18 | "\x2e\x6d\x64\x00\x30\x00\x00\x08" \ 19 | "\xb5\x10\x34\x01\xff\xff\xff\x7f" \ 20 | "\x10\x00\x6c\x69\x62\x00\x00\x04")) 21 | end 22 | context.local(arch: 'amd64') do 23 | assert_equal("REG README.md\nDIR lib\n", 24 | parse("\x92\x22\x0e\x01\x00\x00\x00\x00" \ 25 | "\x3d\xf6\x7c\x45\x8f\x4a\xb3\x41" \ 26 | "\x20\x00\x52\x45\x41\x44\x4d\x45" \ 27 | "\x2e\x6d\x64\x00\x30\x00\x00\x08" \ 28 | "\xb5\x10\x34\x01\x00\x00\x00\x00" \ 29 | "\xff\xff\xff\xff\xff\xff\xff\x7f" \ 30 | "\x18\x00\x6c\x69\x62\x00\x00\x04")) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/util/lists_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: ASCII-8BIT 2 | # frozen_string_literal: true 3 | 4 | require 'test_helper' 5 | 6 | require 'pwnlib/util/lists' 7 | 8 | class FiddlingTest < MiniTest::Test 9 | include ::Pwnlib::Util::Lists 10 | 11 | def test_slice 12 | assert_equal(%w(A B C D), slice(1, 'ABCD')) 13 | assert_equal(%w(AB CD E), slice(2, 'ABCDE')) 14 | assert_equal(%w(AB CD), slice(2, 'ABCDE', underfull_action: :drop)) 15 | assert_equal(%w(AB CD EX), slice(2, 'ABCDE', underfull_action: :fill, fill_value: 'X')) 16 | assert_equal(%w(AB CD EF), slice(2, 'ABCDEF', underfull_action: :fill, fill_value: 'X')) 17 | err = assert_raises(ArgumentError) { slice(2, 'ABCDE', underfull_action: :pusheen) } 18 | assert_equal('underfull_action expect to be one of :ignore, :drop, and :fill', err.message) 19 | err = assert_raises(ArgumentError) { slice(2, 'ABCDE', underfull_action: :fill, fill_value: nil) } 20 | assert_equal('fill_value must be a character', err.message) 21 | end 22 | end 23 | --------------------------------------------------------------------------------