pwntools 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +49 -0
- data/Rakefile +40 -0
- data/lib/pwn.rb +24 -0
- data/lib/pwnlib/constants/constant.rb +45 -0
- data/lib/pwnlib/constants/constants.rb +82 -0
- data/lib/pwnlib/constants/linux/amd64.rb +1558 -0
- data/lib/pwnlib/constants/linux/i386.rb +1340 -0
- data/lib/pwnlib/context.rb +220 -0
- data/lib/pwnlib/dynelf.rb +110 -0
- data/lib/pwnlib/ext/array.rb +21 -0
- data/lib/pwnlib/ext/helper.rb +21 -0
- data/lib/pwnlib/ext/integer.rb +21 -0
- data/lib/pwnlib/ext/string.rb +23 -0
- data/lib/pwnlib/memleak.rb +61 -0
- data/lib/pwnlib/pwn.rb +26 -0
- data/lib/pwnlib/reg_sort.rb +147 -0
- data/lib/pwnlib/util/cyclic.rb +120 -0
- data/lib/pwnlib/util/fiddling.rb +262 -0
- data/lib/pwnlib/util/hexdump.rb +145 -0
- data/lib/pwnlib/util/packing.rb +284 -0
- data/lib/pwnlib/version.rb +5 -0
- data/test/constants/constant_test.rb +24 -0
- data/test/constants/constants_test.rb +31 -0
- data/test/context_test.rb +131 -0
- data/test/data/victim.c +8 -0
- data/test/data/victim32 +0 -0
- data/test/data/victim64 +0 -0
- data/test/dynelf_test.rb +48 -0
- data/test/ext_test.rb +26 -0
- data/test/files/use_pwn.rb +34 -0
- data/test/files/use_pwnlib.rb +19 -0
- data/test/full_file_test.rb +16 -0
- data/test/memleak_test.rb +72 -0
- data/test/reg_sort_test.rb +41 -0
- data/test/test_helper.rb +13 -0
- data/test/util/cyclic_test.rb +36 -0
- data/test/util/fiddling_test.rb +106 -0
- data/test/util/hexdump_test.rb +179 -0
- data/test/util/packing_test.rb +168 -0
- metadata +231 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'pwnlib/constants/constants'
|
5
|
+
require 'pwnlib/context'
|
6
|
+
|
7
|
+
class ConstantsTest < MiniTest::Test
|
8
|
+
include ::Pwnlib::Context
|
9
|
+
Constants = ::Pwnlib::Constants
|
10
|
+
|
11
|
+
def test_amd64
|
12
|
+
context.local(arch: 'amd64') do
|
13
|
+
assert_equal('Constant("SYS_read", 0x0)', Constants.SYS_read.inspect)
|
14
|
+
assert_equal('__NR_arch_prctl', Constants.__NR_arch_prctl.to_s)
|
15
|
+
assert_equal('Constant("(O_CREAT)", 0x40)', Constants.eval('O_CREAT').inspect)
|
16
|
+
# TODO(david942j): implement 'real' Constants.eval
|
17
|
+
# assert_equal('Constant("(O_CREAT | O_WRONLY)", 0x41)', Constants.eval('O_CREAT | O_WRONLY').inspect)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_i386
|
22
|
+
context.local(arch: 'i386') do
|
23
|
+
assert_equal('Constant("SYS_read", 0x3)', Constants.SYS_read.inspect)
|
24
|
+
assert_equal('__NR_prctl', Constants.__NR_prctl.to_s)
|
25
|
+
assert_equal('Constant("(O_CREAT)", 0x40)', Constants.eval('O_CREAT').inspect)
|
26
|
+
assert_equal(0x40, Constants.method(:O_CREAT).call.to_i)
|
27
|
+
# 2 < 3
|
28
|
+
assert_operator(2, :<, Constants.SYS_read)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'pwnlib/context'
|
5
|
+
|
6
|
+
class ContextTest < MiniTest::Test
|
7
|
+
include ::Pwnlib::Context
|
8
|
+
|
9
|
+
def test_update
|
10
|
+
context.update(arch: 'arm', os: 'windows')
|
11
|
+
assert_equal('arm', context.arch)
|
12
|
+
assert_equal('windows', context.os)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_local
|
16
|
+
context.timeout = 1
|
17
|
+
assert_equal(1, context.timeout)
|
18
|
+
|
19
|
+
context.local(timeout: 2) do
|
20
|
+
assert_equal(2, context.timeout)
|
21
|
+
context.timeout = 3
|
22
|
+
assert_equal(3, context.timeout)
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_equal(1, context.timeout)
|
26
|
+
|
27
|
+
assert_raises(RuntimeError) do
|
28
|
+
context.local(timeout: 3) { raise 'QQ failed in block' }
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_equal(1, context.timeout)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_clear
|
35
|
+
default_arch = context.arch
|
36
|
+
context.arch = 'arm'
|
37
|
+
context.clear
|
38
|
+
assert_equal(default_arch, context.arch)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_arch
|
42
|
+
context.arch = 'mips'
|
43
|
+
assert_equal('mips', context.arch)
|
44
|
+
|
45
|
+
err = assert_raises(ArgumentError) { context.arch = 'shik' }
|
46
|
+
assert_match(/arch must be one of/, err.message)
|
47
|
+
assert_equal('mips', context.arch)
|
48
|
+
|
49
|
+
context.clear
|
50
|
+
assert_equal(32, context.bits)
|
51
|
+
context.arch = 'powerpc64'
|
52
|
+
assert_equal(64, context.bits)
|
53
|
+
assert_equal('big', context.endian)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_bits
|
57
|
+
context.bits = 64
|
58
|
+
assert_equal(64, context.bits)
|
59
|
+
|
60
|
+
err = assert_raises(ArgumentError) { context.bits = 0 }
|
61
|
+
assert_match(/bits must be > 0/, err.message)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_bytes
|
65
|
+
context.bytes = 8
|
66
|
+
assert_equal(64, context.bits)
|
67
|
+
assert_equal(8, context.bytes)
|
68
|
+
|
69
|
+
context.bits = 32
|
70
|
+
assert_equal(4, context.bytes)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_endian
|
74
|
+
context.endian = 'le'
|
75
|
+
assert_equal('little', context.endian)
|
76
|
+
|
77
|
+
context.endian = 'big'
|
78
|
+
assert_equal('big', context.endian)
|
79
|
+
|
80
|
+
err = assert_raises(ArgumentError) { context.endian = 'SUPERBIG' }
|
81
|
+
assert_match(/endian must be one of/, err.message)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_log_level
|
85
|
+
context.log_level = 'error'
|
86
|
+
assert_equal(Logger::ERROR, context.log_level)
|
87
|
+
|
88
|
+
context.log_level = 514
|
89
|
+
assert_equal(514, context.log_level)
|
90
|
+
|
91
|
+
err = assert_raises(ArgumentError) { context.log_level = 'BOOM' }
|
92
|
+
assert_match(/log_level must be an integer or one of/, err.message)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_os
|
96
|
+
context.os = 'windows'
|
97
|
+
assert_equal('windows', context.os)
|
98
|
+
|
99
|
+
err = assert_raises(ArgumentError) { context.os = 'deepblue' }
|
100
|
+
assert_match(/os must be one of/, err.message)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_signed
|
104
|
+
context.signed = true
|
105
|
+
assert_equal(true, context.signed)
|
106
|
+
|
107
|
+
context.signed = 'unsigned'
|
108
|
+
assert_equal(false, context.signed)
|
109
|
+
|
110
|
+
err = assert_raises(ArgumentError) { context.signed = 'partial' }
|
111
|
+
assert_match(/signed must be boolean or one of/, err.message)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_timeout
|
115
|
+
context.timeout = 123
|
116
|
+
assert_equal(123, context.timeout)
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_newline
|
120
|
+
context.newline = "\r\n"
|
121
|
+
assert_equal("\r\n", context.newline)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_to_s
|
125
|
+
assert_match(/\APwnlib::Context::ContextType\(.+\)\Z/, context.to_s)
|
126
|
+
end
|
127
|
+
|
128
|
+
def teardown
|
129
|
+
context.clear
|
130
|
+
end
|
131
|
+
end
|
data/test/data/victim.c
ADDED
data/test/data/victim32
ADDED
Binary file
|
data/test/data/victim64
ADDED
Binary file
|
data/test/dynelf_test.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
require 'tty-platform'
|
6
|
+
|
7
|
+
require 'test_helper'
|
8
|
+
require 'pwnlib/dynelf'
|
9
|
+
|
10
|
+
class DynELFTest < MiniTest::Test
|
11
|
+
def test_lookup
|
12
|
+
skip 'Only tested on linux' unless TTY::Platform.new.linux?
|
13
|
+
[32, 64].each do |b|
|
14
|
+
# TODO(hh): Use process instead of popen2
|
15
|
+
Open3.popen2(File.expand_path("../data/victim#{b}", __FILE__)) do |i, o, t|
|
16
|
+
main_ra = Integer(o.readline)
|
17
|
+
libc_path = nil
|
18
|
+
IO.readlines("/proc/#{t.pid}/maps").map(&:split).each do |s|
|
19
|
+
st, ed = s[0].split('-').map { |x| x.to_i(16) }
|
20
|
+
next unless main_ra.between?(st, ed)
|
21
|
+
libc_path = s[-1]
|
22
|
+
break
|
23
|
+
end
|
24
|
+
refute_nil(libc_path)
|
25
|
+
|
26
|
+
# TODO(hh): Use ELF instead of objdump
|
27
|
+
# Methods in libc might have multi-versions, so we record and check if
|
28
|
+
# we can find one of them.
|
29
|
+
h = Hash.new { |hsh, key| hsh[key] = [] }
|
30
|
+
symbols = `objdump -T #{libc_path}`.lines.map(&:split).select { |a| a[2] == 'DF' }
|
31
|
+
symbols.map { |a| h[a[-1]] << a[0].to_i(16) }
|
32
|
+
|
33
|
+
mem = open("/proc/#{t.pid}/mem", 'rb')
|
34
|
+
d = ::Pwnlib::DynELF.new(main_ra) do |addr|
|
35
|
+
mem.seek(addr)
|
36
|
+
mem.getc
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_nil(d.lookup('pipi_hao_wei!'))
|
40
|
+
h.each do |sym, off|
|
41
|
+
assert_includes(off, d.lookup(sym) - d.libbase)
|
42
|
+
end
|
43
|
+
|
44
|
+
i.write('bye')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/ext_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'pwnlib/ext/array'
|
5
|
+
require 'pwnlib/ext/integer'
|
6
|
+
require 'pwnlib/ext/string'
|
7
|
+
|
8
|
+
class ExtTest < MiniTest::Test
|
9
|
+
# Thought that test one method in each module for each type is enough, since it's quite
|
10
|
+
# stupid (and meaningless) to copy the list of proxied functions to here...
|
11
|
+
def test_ext_string
|
12
|
+
assert_equal(0x4142, 'AB'.u16(endian: 'be'))
|
13
|
+
assert_equal([1, 1, 0, 0, 0, 1, 0, 0], "\xC4".bits)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_ext_integer
|
17
|
+
assert_equal('AB', 0x4241.p16)
|
18
|
+
assert_equal([0, 0, 1, 1, 0, 1, 0, 0], 0x34.bits)
|
19
|
+
assert_equal(2**31, 1.bitswap)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_ext_array
|
23
|
+
assert_equal("\xfe", [1, 1, 1, 1, 1, 1, 1, 0].unbits)
|
24
|
+
assert_equal("XX\xef\xbe\xad\xdeXX", ['XX', 0xdeadbeef, 'XX'].flat)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
# Make sure we're using local copy for local testing.
|
4
|
+
$LOAD_PATH.unshift File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
5
|
+
|
6
|
+
require 'pwn'
|
7
|
+
|
8
|
+
context[arch: 'amd64']
|
9
|
+
|
10
|
+
raise 'pack fail' unless pack(1) == "\x01\0\0\0\0\0\0\0"
|
11
|
+
unless ::Pwnlib::Util::Fiddling.__send__(:context).object_id == context.object_id
|
12
|
+
raise 'not unique context'
|
13
|
+
end
|
14
|
+
unless ::Pwnlib::Context.context.object_id == context.object_id
|
15
|
+
raise 'not unique context'
|
16
|
+
end
|
17
|
+
|
18
|
+
# Make sure things aren't polluting Object
|
19
|
+
begin
|
20
|
+
1.__send__(:context)
|
21
|
+
raise 'context polluting Object.'
|
22
|
+
rescue NoMethodError
|
23
|
+
puts 'good'
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
'1'.__send__(:context)
|
28
|
+
raise 'context polluting Object.'
|
29
|
+
rescue NoMethodError
|
30
|
+
puts 'good'
|
31
|
+
end
|
32
|
+
|
33
|
+
# Make sure we can use Util::xxx::yyy directly
|
34
|
+
raise 'pack fail' unless Util::Packing.pack(1) == "\x01\0\0\0\0\0\0\0"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
# Make sure we're using local copy for local testing.
|
4
|
+
$LOAD_PATH.unshift File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
5
|
+
|
6
|
+
# TODO(Darkpi): Should we make sure ALL module works? (maybe we should).
|
7
|
+
require 'pwnlib/util/packing'
|
8
|
+
|
9
|
+
raise 'call from module fail' unless ::Pwnlib::Util::Packing.p8(0x61) == 'a'
|
10
|
+
|
11
|
+
include ::Pwnlib::Util::Packing::ClassMethods
|
12
|
+
raise 'include module and call fail' unless p8(0x61) == 'a'
|
13
|
+
|
14
|
+
begin
|
15
|
+
::Pwnlib::Util::Packing.context
|
16
|
+
raise 'context public in Pwnlib module'
|
17
|
+
rescue NoMethodError
|
18
|
+
puts 'good'
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
require 'test_helper'
|
6
|
+
|
7
|
+
class FullFileTest < MiniTest::Test
|
8
|
+
parallelize_me!
|
9
|
+
Dir['test/files/*.rb'].each do |f|
|
10
|
+
fn = File.basename(f, '.rb')
|
11
|
+
define_method("test_#{fn}") do
|
12
|
+
_, stderr, status = Open3.capture3('ruby', f, binmode: true)
|
13
|
+
assert(status.success?, stderr)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
require 'tty-platform'
|
6
|
+
|
7
|
+
require 'test_helper'
|
8
|
+
require 'pwnlib/memleak'
|
9
|
+
|
10
|
+
class MemLeakTest < MiniTest::Test
|
11
|
+
def setup
|
12
|
+
@victim = IO.binread(File.expand_path('../data/victim32', __FILE__))
|
13
|
+
@leak = ::Pwnlib::MemLeak.new { |addr| @victim[addr] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_find_elf_base_basic
|
17
|
+
assert_equal(0, @leak.find_elf_base(@victim.length * 2 / 3))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_find_elf_base_running
|
21
|
+
skip 'Only tested on linux' unless TTY::Platform.new.linux?
|
22
|
+
[32, 64].each do |b|
|
23
|
+
# TODO(hh): Use process instead of popen2
|
24
|
+
Open3.popen2(File.expand_path("../data/victim#{b}", __FILE__)) do |i, o, t|
|
25
|
+
main_ra = o.readline[2...-1].to_i(16)
|
26
|
+
realbase = nil
|
27
|
+
IO.readlines("/proc/#{t.pid}/maps").map(&:split).each do |s|
|
28
|
+
st, ed = s[0].split('-').map { |x| x.to_i(16) }
|
29
|
+
next unless main_ra.between?(st, ed)
|
30
|
+
realbase = st
|
31
|
+
break
|
32
|
+
end
|
33
|
+
refute_nil(realbase)
|
34
|
+
mem = open("/proc/#{t.pid}/mem", 'rb')
|
35
|
+
l2 = ::Pwnlib::MemLeak.new do |addr|
|
36
|
+
mem.seek(addr)
|
37
|
+
mem.getc
|
38
|
+
end
|
39
|
+
assert_equal(realbase, l2.find_elf_base(main_ra))
|
40
|
+
mem.close
|
41
|
+
i.write('bye')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_n
|
47
|
+
assert_equal("\x7fELF", @leak.n(0, 4))
|
48
|
+
assert_equal(@victim[0xf0, 0x20], @leak.n(0xf0, 0x20))
|
49
|
+
assert_equal(@victim[514, 0x20], @leak.n(514, 0x20))
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_b
|
53
|
+
assert_equal(@victim[0x100], @leak.b(0x100))
|
54
|
+
assert_equal(@victim[514], @leak.b(514))
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_w
|
58
|
+
assert_equal(::Pwnlib::Util::Packing.u16(@victim[0x100, 2]), @leak.w(0x100))
|
59
|
+
assert_equal(::Pwnlib::Util::Packing.u16(@victim[514, 2]), @leak.w(514))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_d
|
63
|
+
assert_equal(::Pwnlib::Util::Packing.u32(@victim[0, 4]), @leak.d(0))
|
64
|
+
assert_equal(::Pwnlib::Util::Packing.u32(@victim[0x100, 4]), @leak.d(0x100))
|
65
|
+
assert_equal(::Pwnlib::Util::Packing.u32(@victim[514, 4]), @leak.d(514))
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_q
|
69
|
+
assert_equal(::Pwnlib::Util::Packing.u64(@victim[0x100, 8]), @leak.q(0x100))
|
70
|
+
assert_equal(::Pwnlib::Util::Packing.u64(@victim[514, 8]), @leak.q(514))
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
require 'test_helper'
|
3
|
+
require 'pwnlib/reg_sort'
|
4
|
+
|
5
|
+
class RegSortTest < MiniTest::Test
|
6
|
+
include ::Pwnlib::RegSort::ClassMethods
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@regs = %w(a b c d x y z)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_normal
|
13
|
+
assert_equal([['mov', 'a', 1], ['mov', 'b', 2]], regsort({ a: 1, b: 2 }, @regs))
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_post_mov
|
17
|
+
assert_equal([['mov', 'a', 1], %w(mov b a)], regsort({ a: 1, b: 1 }, @regs))
|
18
|
+
assert_equal([%w(mov c a), ['mov', 'a', 1], %w(mov b a)], regsort({ a: 1, b: 1, c: 'a' }, @regs))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_pseudoforest
|
22
|
+
# only one connected component
|
23
|
+
assert_equal([%w(mov b a), ['mov', 'a', 1]], regsort({ a: 1, b: 'a' }, @regs))
|
24
|
+
assert_equal([['mov', 'c', 3], %w(xchg a b)], regsort({ a: 'b', b: 'a', c: 3 }, @regs))
|
25
|
+
assert_equal([%w(mov c b), %w(xchg a b)], regsort({ a: 'b', b: 'a', c: 'b' }, @regs))
|
26
|
+
assert_equal([%w(mov x 1), %w(mov y z), %w(mov z c), %w(xchg a b), %w(xchg b c)],
|
27
|
+
regsort({ a: 'b', b: 'c', c: 'a', x: '1', y: 'z', z: 'c' }, @regs))
|
28
|
+
|
29
|
+
# more than one connected components
|
30
|
+
assert_equal([%w(xchg a b), %w(xchg c d)], regsort({ a: 'b', b: 'a', c: 'd', d: 'c' }, @regs))
|
31
|
+
assert_equal([%w(mov c b), %w(mov d b), %w(mov z x), %w(xchg a b), %w(xchg x y)],
|
32
|
+
regsort({ a: 'b', b: 'a', c: 'b', d: 'b', x: 'y', y: 'x', z: 'x' }, @regs))
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_raise
|
36
|
+
err = assert_raises(ArgumentError) do
|
37
|
+
regsort({ a: 1 }, ['b'])
|
38
|
+
end
|
39
|
+
assert_match(/Unknown register!/, err.message)
|
40
|
+
end
|
41
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'codeclimate-test-reporter'
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
5
|
+
[SimpleCov::Formatter::HTMLFormatter, CodeClimate::TestReporter::Formatter]
|
6
|
+
)
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter '/test/'
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'minitest/autorun'
|
12
|
+
require 'minitest/unit'
|
13
|
+
require 'minitest/hell'
|