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.
- checksums.yaml +4 -4
- data/README.md +88 -11
- data/Rakefile +5 -1
- data/lib/pwn.rb +9 -7
- data/lib/pwnlib/abi.rb +60 -0
- data/lib/pwnlib/asm.rb +146 -0
- data/lib/pwnlib/constants/constant.rb +16 -2
- data/lib/pwnlib/constants/constants.rb +35 -19
- data/lib/pwnlib/constants/linux/amd64.rb +30 -1
- data/lib/pwnlib/context.rb +25 -17
- data/lib/pwnlib/dynelf.rb +117 -54
- data/lib/pwnlib/elf/elf.rb +267 -0
- data/lib/pwnlib/ext/helper.rb +4 -4
- data/lib/pwnlib/logger.rb +87 -0
- data/lib/pwnlib/memleak.rb +58 -29
- data/lib/pwnlib/pwn.rb +19 -8
- data/lib/pwnlib/reg_sort.rb +102 -108
- data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
- data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
- data/lib/pwnlib/shellcraft/registers.rb +145 -0
- data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
- data/lib/pwnlib/timer.rb +60 -0
- data/lib/pwnlib/tubes/buffer.rb +96 -0
- data/lib/pwnlib/tubes/sock.rb +95 -0
- data/lib/pwnlib/tubes/tube.rb +270 -0
- data/lib/pwnlib/util/cyclic.rb +95 -94
- data/lib/pwnlib/util/fiddling.rb +256 -220
- data/lib/pwnlib/util/getdents.rb +83 -0
- data/lib/pwnlib/util/hexdump.rb +109 -108
- data/lib/pwnlib/util/lists.rb +55 -0
- data/lib/pwnlib/util/packing.rb +226 -228
- data/lib/pwnlib/util/ruby.rb +18 -0
- data/lib/pwnlib/version.rb +2 -1
- data/test/abi_test.rb +21 -0
- data/test/asm_test.rb +104 -0
- data/test/constants/constant_test.rb +1 -0
- data/test/constants/constants_test.rb +4 -2
- data/test/context_test.rb +1 -0
- data/test/data/echo.rb +20 -0
- data/test/data/elfs/Makefile +22 -0
- data/test/data/elfs/amd64.frelro.elf +0 -0
- data/test/data/elfs/amd64.frelro.pie.elf +0 -0
- data/test/data/elfs/amd64.nrelro.elf +0 -0
- data/test/data/elfs/amd64.prelro.elf +0 -0
- data/test/data/elfs/i386.frelro.pie.elf +0 -0
- data/test/data/elfs/i386.prelro.elf +0 -0
- data/test/data/elfs/source.cpp +19 -0
- data/test/data/flag +1 -0
- data/test/data/lib32/ld.so.2 +0 -0
- data/test/data/lib32/libc.so.6 +0 -0
- data/test/data/lib64/ld.so.2 +0 -0
- data/test/data/lib64/libc.so.6 +0 -0
- data/test/dynelf_test.rb +59 -24
- data/test/elf/elf_test.rb +120 -0
- data/test/ext_test.rb +3 -2
- data/test/files/use_pwnlib.rb +1 -1
- data/test/logger_test.rb +61 -0
- data/test/memleak_test.rb +4 -33
- data/test/reg_sort_test.rb +3 -1
- data/test/shellcraft/infloop_test.rb +26 -0
- data/test/shellcraft/linux/ls_test.rb +108 -0
- data/test/shellcraft/linux/sh_test.rb +119 -0
- data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
- data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
- data/test/shellcraft/memcpy_test.rb +35 -0
- data/test/shellcraft/mov_test.rb +98 -0
- data/test/shellcraft/nop_test.rb +26 -0
- data/test/shellcraft/popad_test.rb +29 -0
- data/test/shellcraft/pushstr_array_test.rb +91 -0
- data/test/shellcraft/pushstr_test.rb +108 -0
- data/test/shellcraft/registers_test.rb +32 -0
- data/test/shellcraft/ret_test.rb +30 -0
- data/test/shellcraft/setregs_test.rb +62 -0
- data/test/shellcraft/shellcraft_test.rb +28 -0
- data/test/test_helper.rb +12 -1
- data/test/timer_test.rb +23 -0
- data/test/tubes/buffer_test.rb +45 -0
- data/test/tubes/sock_test.rb +68 -0
- data/test/tubes/tube_test.rb +241 -0
- data/test/util/cyclic_test.rb +2 -1
- data/test/util/fiddling_test.rb +2 -1
- data/test/util/getdents_test.rb +32 -0
- data/test/util/hexdump_test.rb +7 -9
- data/test/util/lists_test.rb +21 -0
- data/test/util/packing_test.rb +4 -3
- metadata +215 -25
data/lib/pwnlib/ext/helper.rb
CHANGED
@@ -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
|
-
|
14
|
-
|
15
|
-
|
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
|
data/lib/pwnlib/memleak.rb
CHANGED
@@ -3,59 +3,88 @@
|
|
3
3
|
require 'pwnlib/util/packing'
|
4
4
|
|
5
5
|
module Pwnlib
|
6
|
-
#
|
6
|
+
# A class caching and heuristic tool for exploiting memory leaks.
|
7
7
|
class MemLeak
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
data/lib/pwnlib/pwn.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
include ::Pwnlib::Util::Packing::ClassMethods
|
34
|
+
def shellcraft
|
35
|
+
::Pwnlib::Shellcraft::Shellcraft.instance
|
36
|
+
end
|
26
37
|
end
|
data/lib/pwnlib/reg_sort.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
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
|
-
|
114
|
+
result
|
115
|
+
end
|
122
116
|
|
123
|
-
|
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)
|
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
|