pwntools 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -11
  3. data/Rakefile +5 -1
  4. data/lib/pwn.rb +9 -7
  5. data/lib/pwnlib/abi.rb +60 -0
  6. data/lib/pwnlib/asm.rb +146 -0
  7. data/lib/pwnlib/constants/constant.rb +16 -2
  8. data/lib/pwnlib/constants/constants.rb +35 -19
  9. data/lib/pwnlib/constants/linux/amd64.rb +30 -1
  10. data/lib/pwnlib/context.rb +25 -17
  11. data/lib/pwnlib/dynelf.rb +117 -54
  12. data/lib/pwnlib/elf/elf.rb +267 -0
  13. data/lib/pwnlib/ext/helper.rb +4 -4
  14. data/lib/pwnlib/logger.rb +87 -0
  15. data/lib/pwnlib/memleak.rb +58 -29
  16. data/lib/pwnlib/pwn.rb +19 -8
  17. data/lib/pwnlib/reg_sort.rb +102 -108
  18. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
  19. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
  20. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
  21. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
  22. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
  23. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
  33. data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
  34. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
  35. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
  36. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
  37. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
  39. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
  40. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
  41. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
  42. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
  43. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
  46. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
  47. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
  48. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
  49. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
  50. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
  51. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
  52. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
  53. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
  54. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
  55. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
  56. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  57. data/lib/pwnlib/shellcraft/registers.rb +145 -0
  58. data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
  59. data/lib/pwnlib/timer.rb +60 -0
  60. data/lib/pwnlib/tubes/buffer.rb +96 -0
  61. data/lib/pwnlib/tubes/sock.rb +95 -0
  62. data/lib/pwnlib/tubes/tube.rb +270 -0
  63. data/lib/pwnlib/util/cyclic.rb +95 -94
  64. data/lib/pwnlib/util/fiddling.rb +256 -220
  65. data/lib/pwnlib/util/getdents.rb +83 -0
  66. data/lib/pwnlib/util/hexdump.rb +109 -108
  67. data/lib/pwnlib/util/lists.rb +55 -0
  68. data/lib/pwnlib/util/packing.rb +226 -228
  69. data/lib/pwnlib/util/ruby.rb +18 -0
  70. data/lib/pwnlib/version.rb +2 -1
  71. data/test/abi_test.rb +21 -0
  72. data/test/asm_test.rb +104 -0
  73. data/test/constants/constant_test.rb +1 -0
  74. data/test/constants/constants_test.rb +4 -2
  75. data/test/context_test.rb +1 -0
  76. data/test/data/echo.rb +20 -0
  77. data/test/data/elfs/Makefile +22 -0
  78. data/test/data/elfs/amd64.frelro.elf +0 -0
  79. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  80. data/test/data/elfs/amd64.nrelro.elf +0 -0
  81. data/test/data/elfs/amd64.prelro.elf +0 -0
  82. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  83. data/test/data/elfs/i386.prelro.elf +0 -0
  84. data/test/data/elfs/source.cpp +19 -0
  85. data/test/data/flag +1 -0
  86. data/test/data/lib32/ld.so.2 +0 -0
  87. data/test/data/lib32/libc.so.6 +0 -0
  88. data/test/data/lib64/ld.so.2 +0 -0
  89. data/test/data/lib64/libc.so.6 +0 -0
  90. data/test/dynelf_test.rb +59 -24
  91. data/test/elf/elf_test.rb +120 -0
  92. data/test/ext_test.rb +3 -2
  93. data/test/files/use_pwnlib.rb +1 -1
  94. data/test/logger_test.rb +61 -0
  95. data/test/memleak_test.rb +4 -33
  96. data/test/reg_sort_test.rb +3 -1
  97. data/test/shellcraft/infloop_test.rb +26 -0
  98. data/test/shellcraft/linux/ls_test.rb +108 -0
  99. data/test/shellcraft/linux/sh_test.rb +119 -0
  100. data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
  101. data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
  102. data/test/shellcraft/memcpy_test.rb +35 -0
  103. data/test/shellcraft/mov_test.rb +98 -0
  104. data/test/shellcraft/nop_test.rb +26 -0
  105. data/test/shellcraft/popad_test.rb +29 -0
  106. data/test/shellcraft/pushstr_array_test.rb +91 -0
  107. data/test/shellcraft/pushstr_test.rb +108 -0
  108. data/test/shellcraft/registers_test.rb +32 -0
  109. data/test/shellcraft/ret_test.rb +30 -0
  110. data/test/shellcraft/setregs_test.rb +62 -0
  111. data/test/shellcraft/shellcraft_test.rb +28 -0
  112. data/test/test_helper.rb +12 -1
  113. data/test/timer_test.rb +23 -0
  114. data/test/tubes/buffer_test.rb +45 -0
  115. data/test/tubes/sock_test.rb +68 -0
  116. data/test/tubes/tube_test.rb +241 -0
  117. data/test/util/cyclic_test.rb +2 -1
  118. data/test/util/fiddling_test.rb +2 -1
  119. data/test/util/getdents_test.rb +32 -0
  120. data/test/util/hexdump_test.rb +7 -9
  121. data/test/util/lists_test.rb +21 -0
  122. data/test/util/packing_test.rb +4 -3
  123. metadata +215 -25
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Pwnlib
4
4
  module Ext
5
- # Helper methods for defining extension
5
+ # Helper methods for defining extension.
6
6
  module Helper
7
7
  def def_proxy_method(mod, *ms, **m2)
8
8
  ms.flatten
@@ -10,9 +10,9 @@ module Pwnlib
10
10
  .concat(m2.to_a)
11
11
  .each do |method, proxy_to|
12
12
  class_eval <<-EOS
13
- def #{method}(*args, &block)
14
- #{mod}.#{proxy_to}(self, *args, &block)
15
- end
13
+ def #{method}(*args, &block)
14
+ #{mod}.#{proxy_to}(self, *args, &block)
15
+ end
16
16
  EOS
17
17
  end
18
18
  end
@@ -0,0 +1,87 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'logger'
4
+ require 'rainbow'
5
+
6
+ require 'pwnlib/context'
7
+
8
+ module Pwnlib
9
+ # Logging module for printing status during an exploitation, and internally within {Pwnlib}.
10
+ #
11
+ # An exploit developer can use +log+ to print out status messages during an exploitation.
12
+ module Logger
13
+ # The type for logger which inherits Ruby builtin Logger.
14
+ # Main difference is using +context.log_level+ instead of +level+ in logging methods.
15
+ class LoggerType < ::Logger
16
+ SEV_COLOR = {
17
+ 'DEBUG' => '#ff5f5f',
18
+ 'INFO' => '#87ff00',
19
+ 'WARN' => '#ffff00',
20
+ 'ERROR' => '#ff5f00',
21
+ 'FATAL' => '#ff0000'
22
+ }.freeze
23
+
24
+ # Instantiate a {Pwnlib::Logger::LoggerType} object.
25
+ def initialize
26
+ super(STDOUT)
27
+ @formatter = proc do |severity, _datetime, _progname, msg|
28
+ format("[%s] %s\n", Rainbow(severity).color(SEV_COLOR[severity]), msg)
29
+ end
30
+ end
31
+
32
+ # Log the message with indent.
33
+ #
34
+ # @param [String] message
35
+ # The message to log.
36
+ # @param [DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN] level
37
+ # The severity of the message.
38
+ def indented(message, level: DEBUG)
39
+ return if @logdev.nil? || level < context.log_level
40
+ @logdev.write(
41
+ message.lines.map { |s| ' ' + s }.join + "\n"
42
+ )
43
+ true
44
+ end
45
+
46
+ private
47
+
48
+ def add(severity, message = nil, progname = nil)
49
+ severity ||= UNKNOWN
50
+ return true if severity < context.log_level
51
+ super(severity, message, progname)
52
+ end
53
+
54
+ include ::Pwnlib::Context
55
+ end
56
+
57
+ @log = LoggerType.new
58
+
59
+ # @!attribute [r] logger
60
+ # @return [LoggerType] the singleton logger for all classes.
61
+ class << self
62
+ attr_reader :log
63
+ end
64
+
65
+ # Include this module to use logger.
66
+ # Including {Pwnlib::Logger} from module M would add +log+ as a private instance method and a private class
67
+ # method for module M.
68
+ # @!visibility private
69
+ module IncludeLogger
70
+ private
71
+
72
+ def log
73
+ ::Pwnlib::Logger.log
74
+ end
75
+ end
76
+
77
+ # @!visibility private
78
+ def self.included(base)
79
+ base.include(IncludeLogger)
80
+ class << base
81
+ include IncludeLogger
82
+ end
83
+ end
84
+
85
+ include ::Logger::Severity
86
+ end
87
+ end
@@ -3,59 +3,88 @@
3
3
  require 'pwnlib/util/packing'
4
4
 
5
5
  module Pwnlib
6
- # MemLeak is a caching and heuristic tool for exploiting memory leaks.
6
+ # A class caching and heuristic tool for exploiting memory leaks.
7
7
  class MemLeak
8
- PAGE_SIZE = 0x1000
9
- PAGE_MASK = ~(PAGE_SIZE - 1)
10
-
8
+ # Instantiate a {Pwnlib::MemLeak} object.
9
+ #
10
+ # @yieldparam [Integer] leak_addr
11
+ # The start address that the leaker should leak from.
12
+ #
13
+ # @yieldreturn [String]
14
+ # A leaked non-empty byte string, starting from +leak_addr+.
11
15
  def initialize(&block)
12
16
  @leak = block
13
- @base = nil
14
17
  @cache = {}
15
18
  end
16
19
 
17
- def find_elf_base(ptr)
18
- ptr &= PAGE_MASK
19
- loop do
20
- return @base = ptr if n(ptr, 4) == "\x7fELF"
21
- ptr -= PAGE_SIZE
22
- end
23
- end
24
-
25
- # Call the leaker function on address `addr`.
26
- # Store the result to @cache
27
- def do_leak(addr)
28
- unless @cache.key?(addr)
29
- data = @leak.call(addr)
30
- data.bytes.each.with_index(addr) { |b, i| @cache[i] = b }
31
- end
32
- @cache[addr]
33
- end
34
-
35
- # Leak `numb` bytes at `addr`.
20
+ # Leak +numb+ bytes at +addr+.
36
21
  # Returns a string with the leaked bytes.
22
+ #
23
+ # @param [Integer] addr
24
+ # The starting address of the leak.
25
+ # @param [Integer] numb
26
+ # Number of bytes to be leaked.
27
+ #
28
+ # @return [String]
29
+ # The leaked byte string.
37
30
  def n(addr, numb)
38
31
  (0...numb).map { |i| do_leak(addr + i) }.pack('C*')
39
32
  end
40
33
 
41
- # Leak byte at ``((uint8_t*) addr)[ndx]``
34
+ # Leak a byte at +*((uint8_t*) addr)+.
35
+ #
36
+ # @param [Integer] addr
37
+ # The address of the leak.
38
+ #
39
+ # @return [Integer]
40
+ # The leaked byte.
42
41
  def b(addr)
43
- n(addr, 1)
42
+ Util::Packing.u8(n(addr, 1))
44
43
  end
45
44
 
46
- # Leak word at ``((uint16_t*) addr)[ndx]``
45
+ # Leak a word at +*((uint16_t*) addr)+.
46
+ #
47
+ # @param [Integer] addr
48
+ # The address of the leak.
49
+ #
50
+ # @return [Integer]
51
+ # The leaked word.
47
52
  def w(addr)
48
53
  Util::Packing.u16(n(addr, 2))
49
54
  end
50
55
 
51
- # Leak dword at ``((uint32_t*) addr)[ndx]``
56
+ # Leak a dword at +*((uint32_t*) addr)+.
57
+ #
58
+ # @param [Integer] addr
59
+ # The address of the leak.
60
+ #
61
+ # @return [Integer]
62
+ # The leaked dword.
52
63
  def d(addr)
53
64
  Util::Packing.u32(n(addr, 4))
54
65
  end
55
66
 
56
- # Leak qword at ``((uint64_t*) addr)[ndx]``
67
+ # Leak a qword at +*((uint64_t*) addr)+.
68
+ #
69
+ # @param [Integer] addr
70
+ # The address of the leak.
71
+ #
72
+ # @return [Integer]
73
+ # The leaked qword.
57
74
  def q(addr)
58
75
  Util::Packing.u64(n(addr, 8))
59
76
  end
77
+
78
+ private
79
+
80
+ # Call the leaker function on address +addr+.
81
+ # The result would be cached.
82
+ def do_leak(addr)
83
+ unless @cache.key?(addr)
84
+ data = @leak.call(addr)
85
+ data.bytes.each.with_index(addr) { |b, i| @cache[i] = b }
86
+ end
87
+ @cache[addr]
88
+ end
60
89
  end
61
90
  end
@@ -1,26 +1,37 @@
1
1
  # encoding: ASCII-8BIT
2
2
 
3
- # require this file would also require all things in pwnlib, but would not
4
- # pollute anything.
3
+ # require this file would also require all things in pwnlib, but would not pollute anything.
5
4
 
5
+ require 'pwnlib/asm'
6
6
  require 'pwnlib/constants/constant'
7
7
  require 'pwnlib/constants/constants'
8
8
  require 'pwnlib/context'
9
9
  require 'pwnlib/dynelf'
10
+ require 'pwnlib/elf/elf'
11
+ require 'pwnlib/logger'
10
12
  require 'pwnlib/reg_sort'
13
+ require 'pwnlib/shellcraft/shellcraft'
14
+ require 'pwnlib/tubes/sock'
11
15
 
12
16
  require 'pwnlib/util/cyclic'
13
17
  require 'pwnlib/util/fiddling'
18
+ require 'pwnlib/util/getdents'
14
19
  require 'pwnlib/util/hexdump'
20
+ require 'pwnlib/util/lists'
15
21
  require 'pwnlib/util/packing'
16
22
 
17
- # include this module in a class to use all pwnlib functions in that class
18
- # instance.
23
+ # include this module in a class to use all pwnlib functions in that class instance.
19
24
  module Pwn
25
+ include ::Pwnlib::Asm
20
26
  include ::Pwnlib::Context
27
+ include ::Pwnlib::Logger
28
+ include ::Pwnlib::Util::Cyclic
29
+ include ::Pwnlib::Util::Fiddling
30
+ include ::Pwnlib::Util::HexDump
31
+ include ::Pwnlib::Util::Lists
32
+ include ::Pwnlib::Util::Packing
21
33
 
22
- include ::Pwnlib::Util::Cyclic::ClassMethods
23
- include ::Pwnlib::Util::Fiddling::ClassMethods
24
- include ::Pwnlib::Util::HexDump::ClassMethods
25
- include ::Pwnlib::Util::Packing::ClassMethods
34
+ def shellcraft
35
+ ::Pwnlib::Shellcraft::Shellcraft.instance
36
+ end
26
37
  end
@@ -1,127 +1,122 @@
1
1
  # encoding: ASCII-8BIT
2
2
 
3
3
  require 'pwnlib/context'
4
+ require 'pwnlib/util/ruby'
4
5
 
5
6
  module Pwnlib
6
7
  # Do topological sort on register assignments.
7
8
  module RegSort
8
- # @note Do not create and call instance method here. Instead, call module method on {RegSort}.
9
- module ClassMethods
10
- # Sorts register dependencies.
11
- #
12
- # Given a dictionary of registers to desired register contents,
13
- # return the optimal order in which to set the registers to
14
- # those contents.
15
- #
16
- # The implementation assumes that it is possible to move from
17
- # any register to any other register.
18
- #
19
- # @param [Hash<Symbol, String => Object>] in_out
20
- # Dictionary of desired register states.
21
- # Keys are registers, values are either registers or any other value.
22
- # @param [Array<String>] all_regs
23
- # List of all possible registers.
24
- # Used to determine which values in +in_out+ are registers, versus
25
- # regular values.
26
- # @option [Boolean] randomize
27
- # Randomize as much as possible about the order or registers.
28
- #
29
- # @return [Array]
30
- # Array of instructions, see examples for more details.
31
- #
32
- # @example
33
- # regs = %w(a b c d x y z)
34
- # regsort({a: 1, b: 2}, regs)
35
- # => [['mov', 'a', 1], ['mov', 'b', 2]]
36
- # regsort({a: 'b', b: 'a'}, regs)
37
- # => [['xchg', 'a', 'b']]
38
- # regsort({a: 1, b: 'a'}, regs)
39
- # => [['mov', 'b', 'a'], ['mov', 'a', 1]]
40
- # regsort({a: 'b', b: 'a', c: 3}, regs)
41
- # => [['mov', 'c', 3], ['xchg', 'a', 'b']]
42
- # regsort({a: 'b', b: 'a', c: 'b'}, regs)
43
- # => [['mov', 'c', 'b'], ['xchg', 'a', 'b']]
44
- # regsort({a: 'b', b: 'c', c: 'a', x: '1', y: 'z', z: 'c'}, regs)
45
- # => [['mov', 'x', '1'],
46
- # ['mov', 'y', 'z'],
47
- # ['mov', 'z', 'c'],
48
- # ['xchg', 'a', 'b'],
49
- # ['xchg', 'b', 'c']]
50
- #
51
- # @note
52
- # Different from python-pwntools, we don't support +tmp+/+xchg+ options
53
- # because there's no such usage at all.
54
- def regsort(in_out, all_regs, randomize: nil)
55
- # randomize = context.randomize if randomize.nil?
9
+ module_function
56
10
 
57
- # TODO(david942j): stringify_keys
58
- in_out = in_out.map { |k, v| [k.to_s, v] }.to_h
59
- # Drop all registers which will be set to themselves.
60
- # Ex. {eax: 'eax'}
61
- in_out.reject! { |k, v| k == v }
11
+ # Sorts register dependencies.
12
+ #
13
+ # Given a dictionary of registers to desired register contents, return the optimal order in which to set the
14
+ # registers to those contents.
15
+ #
16
+ # The implementation assumes that it is possible to move from any register to any other register.
17
+ #
18
+ # @param [Hash<Symbol, String => Object>] in_out
19
+ # Dictionary of desired register states.
20
+ # Keys are registers, values are either registers or any other value.
21
+ # @param [Array<String>] all_regs
22
+ # List of all possible registers.
23
+ # Used to determine which values in +in_out+ are registers, versus regular values.
24
+ # @param [Boolean] randomize
25
+ # Randomize as much as possible about the order or registers.
26
+ #
27
+ # @return [Array]
28
+ # Array of instructions, see examples for more details.
29
+ #
30
+ # @diff We don't support +tmp+/+xchg+ options because there's no such usage at all.
31
+ #
32
+ # @example
33
+ # regs = %w(a b c d x y z)
34
+ # regsort({a: 1, b: 2}, regs)
35
+ # #=> [['mov', 'a', 1], ['mov', 'b', 2]]
36
+ # regsort({a: 'b', b: 'a'}, regs)
37
+ # #=> [['xchg', 'a', 'b']]
38
+ # regsort({a: 1, b: 'a'}, regs)
39
+ # #=> [['mov', 'b', 'a'], ['mov', 'a', 1]]
40
+ # regsort({a: 'b', b: 'a', c: 3}, regs)
41
+ # #=> [['mov', 'c', 3], ['xchg', 'a', 'b']]
42
+ # regsort({a: 'b', b: 'a', c: 'b'}, regs)
43
+ # #=> [['mov', 'c', 'b'], ['xchg', 'a', 'b']]
44
+ # regsort({a: 'b', b: 'c', c: 'a', x: '1', y: 'z', z: 'c'}, regs)
45
+ # #=> [['mov', 'x', '1'],
46
+ # ['mov', 'y', 'z'],
47
+ # ['mov', 'z', 'c'],
48
+ # ['xchg', 'a', 'b'],
49
+ # ['xchg', 'b', 'c']]
50
+ def regsort(in_out, all_regs, randomize: nil)
51
+ # randomize = context.randomize if randomize.nil?
62
52
 
63
- # Check input
64
- if (in_out.keys - all_regs).any?
65
- raise ArgumentError, format('Unknown register! Know: %p. Got: %p', all_regs, in_out)
66
- end
53
+ # TODO(david942j): stringify_keys
54
+ in_out = in_out.map { |k, v| [k.to_s, v] }.to_h
55
+ # Drop all registers which will be set to themselves.
56
+ # Ex. {eax: 'eax'}
57
+ in_out.reject! { |k, v| k == v }
67
58
 
68
- # Collapse constant values
69
- #
70
- # Ex. {eax: 1, ebx: 1} can be collapsed to {eax: 1, ebx: 'eax'}.
71
- # +post_mov+ are collapsed registers, set their values in the end.
72
- post_mov = in_out.group_by { |_, v| v }.each_value.with_object({}) do |list, hash|
73
- list.sort!
74
- first_reg, val = list.shift
75
- # Special case for val.zero? because zeroify registers cost cheaper than mov.
76
- next if list.empty? || all_regs.include?(val) || val.zero?
77
- list.each do |reg, _|
78
- hash[reg] = first_reg
79
- in_out.delete(reg)
80
- end
81
- end
59
+ # Check input
60
+ if (in_out.keys - all_regs).any?
61
+ raise ArgumentError, format('Unknown register! Know: %p. Got: %p', all_regs, in_out)
62
+ end
82
63
 
83
- graph = in_out.dup
84
- result = []
64
+ # Collapse constant values.
65
+ #
66
+ # Ex. {eax: 1, ebx: 1} can be collapsed to {eax: 1, ebx: 'eax'}.
67
+ # +post_mov+ are collapsed registers, set their values in the end.
68
+ post_mov = in_out.group_by { |_, v| v }.each_value.with_object({}) do |list, hash|
69
+ list.sort!
70
+ first_reg, val = list.shift
71
+ # Special case for val.zero? because zeroify registers is cheaper than mov.
72
+ next if list.empty? || all_regs.include?(val) || val.zero?
73
+ list.each do |reg, _|
74
+ hash[reg] = first_reg
75
+ in_out.delete(reg)
76
+ end
77
+ end
85
78
 
86
- # Let's do the topological sort.
87
- # so sad ruby 2.1 doesn't have +itself+...
88
- deg = graph.values.group_by { |i| i }.map { |k, v| [k, v.size] }.to_h
89
- graph.each_key { |k| deg[k] ||= 0 }
79
+ graph = in_out.dup
80
+ result = []
90
81
 
91
- until deg.empty?
92
- min_deg = deg.min_by { |_, v| v }[1]
93
- break unless min_deg.zero? # remain are all cycles
94
- min_pivs = deg.select { |_, v| v == min_deg }
95
- piv = randomize ? min_pivs.sample : min_pivs.first
96
- dst = piv.first
97
- deg.delete(dst)
98
- next unless graph.key?(dst) # Reach an end node.
99
- deg[graph[dst]] -= 1
100
- result << ['mov', dst, graph[dst]]
101
- graph.delete(dst)
102
- end
82
+ # Let's do the topological sort.
83
+ # so sad ruby 2.1 doesn't have +itself+...
84
+ deg = graph.values.group_by { |i| i }.map { |k, v| [k, v.size] }.to_h
85
+ graph.each_key { |k| deg[k] ||= 0 }
103
86
 
104
- # Remain must be cycles.
105
- graph.each_key do |reg|
106
- cycle = check_cycle(reg, graph)
107
- cycle.each_cons(2) do |d, s|
108
- result << ['xchg', d, s]
109
- end
110
- cycle.each { |r| graph.delete(r) }
111
- end
87
+ until deg.empty?
88
+ min_deg = deg.min_by { |_, v| v }[1]
89
+ break unless min_deg.zero? # remain are all cycles
90
+ min_pivs = deg.select { |_, v| v == min_deg }
91
+ piv = randomize ? min_pivs.sample : min_pivs.first
92
+ dst = piv.first
93
+ deg.delete(dst)
94
+ next unless graph.key?(dst) # Reach an end node.
95
+ deg[graph[dst]] -= 1
96
+ result << ['mov', dst, graph[dst]]
97
+ graph.delete(dst)
98
+ end
112
99
 
113
- # Now assign those collapsed registers.
114
- post_mov.sort.each do |dreg, sreg|
115
- result << ['mov', dreg, sreg]
100
+ # Remain must be cycles.
101
+ graph.each_key do |reg|
102
+ cycle = check_cycle(reg, graph)
103
+ cycle.each_cons(2) do |d, s|
104
+ result << ['xchg', d, s]
116
105
  end
106
+ cycle.each { |r| graph.delete(r) }
107
+ end
117
108
 
118
- result
109
+ # Now assign those collapsed registers.
110
+ post_mov.sort.each do |dreg, sreg|
111
+ result << ['mov', dreg, sreg]
119
112
  end
120
113
 
121
- private
114
+ result
115
+ end
122
116
 
123
- # Walk down the assignment list of a register,
124
- # return the path walked if it is encountered again.
117
+ Pwnlib::Util::Ruby.private_class_method_block do
118
+ # Walk down the assignment list of a register, return the path walked if it is encountered again.
119
+ #
125
120
  # @example
126
121
  # check_cycle('a', {'a' => 1}) #=> []
127
122
  # check_cycle('a', {'a' => 'a'}) #=> ['a']
@@ -132,16 +127,15 @@ module Pwnlib
132
127
  check_cycle_(reg, assignments, [])
133
128
  end
134
129
 
135
- def check_cycle_(reg, assignments, path) # :nodoc:
130
+ def check_cycle_(reg, assignments, path)
136
131
  target = assignments[reg]
137
132
  path << reg
138
- # No cycle, some other value (e.g. 1)
133
+ # No cycle, some other value (e.g. 1).
139
134
  return [] unless assignments.key?(target)
140
- # Found a cycle
135
+ # Found a cycle.
141
136
  return target == path.first ? path : [] if path.include?(target)
142
137
  check_cycle_(target, assignments, path)
143
138
  end
144
139
  end
145
- extend ClassMethods
146
140
  end
147
141
  end