pwntools 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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