heapinfo 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bb558249510b9c160a5f0f699d702a17b1189624
4
- data.tar.gz: dacc4780724a2254d8137a23d2f920734db9034d
3
+ metadata.gz: 22b8757e3a48390494b019943a52754a154051c3
4
+ data.tar.gz: 4b4af3bf4d67e0ca35b9f5f8feb537499565f56e
5
5
  SHA512:
6
- metadata.gz: 8fce3e42275b5895a2bec83dbe31d216d1d5a7506905e4d0e527f449c4418f9af00d6f9133d8b1074bb293e7a33ac0557833539562d68dd7a7cfc22d04404885
7
- data.tar.gz: 1d036055d53f75e3d9639af6274682b29f5d8f6b275d4644d2619fa29ce4bbed559871ea9caf4b72c66fe81a82dcf4bd0496de239fd88b807df8b50757790ef5
6
+ metadata.gz: 3872db0f32aaa2fbbedcca004163cb0b5181495cc81605a6587ef5c6dfcebf60f43209146aeda541c716cbdb6bca2bd2770927643cf3bfe50ade2cf7b709a282
7
+ data.tar.gz: 82a57d0254a650ba531b5f00db676acc3ef4635cd0dbcdc34877f5a09ea8b369667377d96456e466e0a20ad2a6fb3dcb3737cb05966c95cd2483315bb24b7a8c
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [![Build Status](https://travis-ci.org/david942j/heapinfo.svg?branch=master)](https://travis-ci.org/david942j/heapinfo)
2
- ![](http://ruby-gem-downloads-badge.herokuapp.com/heapinfo?type=total)
2
+ [![Downloads](http://ruby-gem-downloads-badge.herokuapp.com/heapinfo?type=total)](https://rubygems.org/gems/heapinfo)
3
3
  [![Code Climate](https://codeclimate.com/github/david942j/heapinfo/badges/gpa.svg)](https://codeclimate.com/github/david942j/heapinfo)
4
4
  [![Issue Count](https://codeclimate.com/github/david942j/heapinfo/badges/issue_count.svg)](https://codeclimate.com/github/david942j/heapinfo)
5
5
  [![Test Coverage](https://codeclimate.com/github/david942j/heapinfo/badges/coverage.svg)](https://codeclimate.com/github/david942j/heapinfo/coverage)
@@ -7,7 +7,7 @@
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
8
8
 
9
9
  ## HeapInfo
10
- As pwn lovers, while playing CTF with heap exploitation, we always need a debugger (e.g. gdb) for tracking memory layout. But we don't really need a debugger if we just want to see whether the heap layout same as our imagine or not. Hope this small tool helps us exploit easier ;).
10
+ As pwn lovers, while playing CTF with heap exploitation, we always need a debugger (e.g. gdb) for tracking memory layout. But we don't really need gdb if we want to see whether the heap layout same as our imagine or not. Hope this small tool helps us exploit easier ;).
11
11
 
12
12
  Implement with ruby because I love ruby :P. But might also implement with Python (if no others did) in the future.
13
13
 
@@ -15,7 +15,7 @@ If you prefer [pwntools](https://github.com/Gallopsled/pwntools) for exploiting,
15
15
 
16
16
  Any suggestion of features or bug issues is welcome.
17
17
 
18
- Relation works are [pwntools-ruby](https://github.com/peter50216/pwntools-ruby) and [gdbpwn](https://github.com/scwuaptx/Pwngdb).
18
+ Related works are [pwntools-ruby](https://github.com/peter50216/pwntools-ruby) and [gdbpwn](https://github.com/scwuaptx/Pwngdb).
19
19
 
20
20
  ## Install
21
21
  **HeapInfo** is still under developing for more features, so the version might change frequently :p
@@ -25,14 +25,18 @@ $ gem install heapinfo
25
25
  ```
26
26
 
27
27
  ## Features
28
- * Can use in your ruby exploit script or in irb/pry
29
- * **HeapInfo** works when the `victim` is being traced! i.e. you can use ltrace/strace/gdb and **HeapInfo** simultaneously!
30
- * `dump` - dump arbitrarily address memory.
31
- * `layouts` - show the current bin layouts, very useful for heap exploitation.
28
+ * Can use in your ruby exploit script or in irb/pry.
29
+ * **HeapInfo** works when `victim` is being traced! i.e. you can use ltrace/strace/gdb and **HeapInfo** simultaneously!
30
+ * `dump` - Dump arbitrarily address memory.
31
+ * `layouts` - Show the current bin layouts, very useful for heap exploitation.
32
+ * `offset` - Show the offset between given address and segment. Very useful for calculating relative offset.
32
33
  * `x` - Provide gdb-like commands.
33
34
  * `find` - Provide gdb-like commands.
34
35
  * More features and details can be found in [RDoc](http://www.rubydoc.info/github/david942j/heapinfo/master/)
35
36
 
37
+ ### Under developing
38
+ * `free` - Show what will happend when `free` an address.
39
+
36
40
  ## Usage
37
41
 
38
42
  #### Load
@@ -68,48 +72,60 @@ NOTICE: While the process is not found, most methods will return `nil`. One way
68
72
  h = heapinfo('remote')
69
73
  # Process not found
70
74
  h.pid # nil
71
- h.debug {
72
- fail unless leak_libc_base == h.libc.base
73
- # wrapper with `debug` so that no error will be raised when pwning remote service
74
- }
75
+
76
+ # wrapper with `debug` so that no error will be raised when pwning remote service
77
+ h.debug { fail unless leak_libc_base == h.libc.base }
75
78
  ```
76
79
 
77
80
  #### Dump
78
81
  Query content of specific address.
79
82
 
80
- NOTICE: you MUST have permission of attaching a program, otherwise dump will fail.
83
+ NOTICE: You MUST have permission of attaching a program, otherwise dump will fail.
81
84
 
82
85
  i.e. `/proc/sys/kernel/yama/ptrace_scope` set to 0 or run as root.
83
86
 
84
87
  ```ruby
85
- h.debug {
88
+ h.debug do
86
89
  p h.dump(:libc, 8)
87
90
  # => "\x7FELF\x02\x01\x01\x00"
88
91
  p h.dump(:heap, 16)
89
92
  # => "\x00\x00\x00\x00\x00\x00\x00\x00\x31\x00\x00\x00\x00\x00\x00\x00"
90
- p h.dump('heap+0x30, 16') # support offset!
93
+ p h.dump('heap+0x30', 16) # support offset!
91
94
  # => "\x00\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00"
92
- p h.dump(:elf, 8)
95
+ p h.dump('heap+0x30 * 3 + 0x8', 16) # and even complex formula!
96
+ # => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
97
+ p h.dump(:program, 8)
93
98
  # => "\x7FELF\x02\x01\x01\x00"
94
- p h.dump(0x400000, 8) # or simply give addr
99
+ p h.dump(0x400000, 8) # or simply give address
95
100
  # => "\x7FELF\x02\x01\x01\x00"
96
- }
97
- # invalid examples:
101
+ end
102
+
103
+ # invalid example:
98
104
  # h.dump('meow') # no such segment
99
- # h.dump('heap-1, 64') # not support `-`
100
105
  ```
101
106
 
102
107
  #### layouts
103
108
  ```ruby
104
- h.layouts :fastbin
109
+ h.layouts :fast
105
110
  ```
106
111
  ![fastbin layouts](https://github.com/david942j/heapinfo/blob/master/examples/fastbin_layouts.png?raw=true)
107
112
 
108
113
  ```ruby
109
- h.layouts :unsorted_bin, :smallbin
114
+ h.layouts :unsorted, :small
110
115
  ```
111
116
  ![smallbin layouts](https://github.com/david942j/heapinfo/blob/master/examples/unsorted_smallbin_layouts.png?raw=true)
112
117
 
118
+ #### offset
119
+ ```ruby
120
+ h.offset(0x7fda86fe8670)
121
+ # 0xf6670 after libc
122
+ h.offset(0x1839cd0, :heap) # specific segment name
123
+ # 0x20cd0 after heap
124
+ h.offset(0x1839cd0)
125
+ # 0x20cd0 after heap
126
+ ```
127
+ ![offset](https://github.com/david942j/heapinfo/blob/master/examples/offset.png?raw=true)
128
+
113
129
  #### x - gdb-like command
114
130
  ```ruby
115
131
  h.x 8, :heap
@@ -128,6 +144,12 @@ h.find(/E.F/, 0x400000, 4)
128
144
  # => 4194305 # 0x400001
129
145
  h.find(/E.F/, 0x400000, 3)
130
146
  # => nil
131
- sh_offset = h.find('/bin/sh', :libc) - h.libc.base
132
- # => 1559771 # 0x17ccdb
147
+ h.offset(h.find('/bin/sh', :libc))
148
+ # 0x18c177 after libc
133
149
  ```
150
+
151
+ ## Tests
152
+ **HeapInfo** currently only run tests on ubuntu, followings are tested glibc versions:
153
+ * libc-2.19
154
+ * libc-2.23
155
+ * libc-2.24
@@ -19,8 +19,8 @@ module HeapInfo
19
19
  # @param [String, Fixnum] prog
20
20
  # The program name of victim. If a number is given, seem as pid (useful when multi-processes exist).
21
21
  # @param [Hash] options Give library's file name.
22
- # @option options [String, Regexp] :libc file name of glibc, default is <tt>/libc[^\w]/</tt>
23
- # @option options [String, Regexp] :ld file name of dynamic linker/loader, default is <tt>/\/ld-.+\.so/</tt>
22
+ # @option options [String, Regexp] :libc file name of glibc, default is +/bc[^a-z]*\.so/+.
23
+ # @option options [String, Regexp] :ld file name of dynamic linker/loader, default is +/\/ld-.+\.so/+.
24
24
  # @return [HeapInfo::Process] The object for further usage
25
25
  # @example
26
26
  # h = heapinfo './victim'
@@ -13,7 +13,7 @@ module HeapInfo
13
13
  attr_reader :smallbin
14
14
  # @return [Integer] The +system_mem+ in arena.
15
15
  attr_reader :system_mem
16
- # attr_reader :largebin, :last_remainder
16
+ # attr_reader :largebin
17
17
 
18
18
  # Instantiate a {HeapInfo::Arena} object.
19
19
  #
@@ -60,12 +60,13 @@ module HeapInfo
60
60
  # @param [Symbol] args Bin type(s) you want to see.
61
61
  # @return [String] Bin layouts that wrapper with color codes.
62
62
  # @example
63
- # puts h.libc.main_arena.layouts :fastbin, :unsorted_bin, :smallbin
63
+ # puts h.libc.main_arena.layouts(:fast, :unsorted, :small)
64
64
  def layouts(*args)
65
+ args = args.map(&:to_s).join('|')
65
66
  res = ''
66
- res += fastbin.map(&:inspect).join if args.include? :fastbin
67
- res += unsorted_bin.inspect if args.include? :unsorted_bin
68
- res += smallbin.map(&:inspect).join if args.include? :smallbin
67
+ res += fastbin.map(&:inspect).join if args.include?('fast')
68
+ res += unsorted_bin.inspect if args.include?('unsort')
69
+ res += smallbin.map(&:inspect).join if args.include?('small')
69
70
  res
70
71
  end
71
72
 
@@ -2,65 +2,73 @@ require 'digest'
2
2
  module HeapInfo
3
3
  # Self implment file-base cache manager.
4
4
  #
5
- # Values are recorded in files based on <tt>Marshal</tt>.
5
+ # Values are recorded in files based on +Marshal+.
6
6
  module Cache
7
7
  # Directory for caching files.
8
- # e.g. HeapInfo will record main_arena_offset for glibc(s)
8
+ # e.g. HeapInfo will record main_arena_offset for glibc(s).
9
9
  CACHE_DIR = File.join(ENV['HOME'], '.cache/heapinfo')
10
10
 
11
- # Get the key for store libc offsets
12
- #
13
- # @param [String] libc_path The realpath to libc file
14
- # @return [String] The key for cache read/write.
15
- def self.key_libc_offset(libc_path)
16
- File.join('libc', Digest::MD5.hexdigest(IO.binread(libc_path)), 'offset')
17
- end
11
+ # Define class methods.
12
+ module ClassMethods
13
+ # Get the key for store libc offsets.
14
+ #
15
+ # @param [String] libc_path The realpath to libc file.
16
+ # @return [String] The key for cache read/write.
17
+ def key_libc_offset(libc_path)
18
+ File.join('libc', Digest::MD5.hexdigest(IO.binread(libc_path)), 'offset')
19
+ end
18
20
 
19
- # Write cache to file.
20
- #
21
- # @param [String] key In file path format, only accept <tt>[\w\/]</tt> to prevent horrible things.
22
- # @param [Object] value <tt>value</tt> will be stored with <tt>#Marshal::dump</tt>.
23
- # @return [Boolean] true
24
- def self.write(key, value)
25
- filepath = realpath key
26
- FileUtils.mkdir_p(File.dirname(filepath))
27
- IO.binwrite(filepath, Marshal.dump(value))
28
- true
29
- end
21
+ # Write cache to file.
22
+ #
23
+ # @param [String] key In file path format, only accept +[\w/]+ to prevent horrible things.
24
+ # @param [Object] value +value+ will be stored with +Marshal#dump+.
25
+ # @return [Boolean] true
26
+ def write(key, value)
27
+ filepath = realpath(key)
28
+ FileUtils.mkdir_p(File.dirname(filepath))
29
+ IO.binwrite(filepath, Marshal.dump(value))
30
+ true
31
+ end
30
32
 
31
- # Read cache from file.
32
- #
33
- # @param [String] key In file path format, only accept <tt>[\w\/]</tt> to prevent horrible things.
34
- # @return [Object, NilClass] value that recorded, return <tt>nil</tt> when cache miss.
35
- def self.read(key)
36
- filepath = realpath key
37
- return unless File.file?(filepath)
38
- Marshal.load(IO.binread(filepath))
39
- rescue
40
- nil # handle if file content invalid
41
- end
33
+ # Read cache from file.
34
+ #
35
+ # @param [String] key In file path format, only accept +[\w\/]+ to prevent horrible things.
36
+ # @return [Object, NilClass] Value that recorded, return +nil+ when cache miss.
37
+ def read(key)
38
+ filepath = realpath(key)
39
+ return unless File.file?(filepath)
40
+ Marshal.load(IO.binread(filepath))
41
+ rescue
42
+ nil # handle if file content invalid
43
+ end
42
44
 
43
- # @param [String] key
44
- # @return [String] Prepend with <tt>CACHE_DIR</tt>
45
- def self.realpath(key)
46
- raise ArgumentError, 'Invalid key(file path)' if key =~ %r{[^\w/]}
47
- File.join(CACHE_DIR, key)
48
- end
45
+ # Clear the cache directory.
46
+ # @return [void]
47
+ def clear_all
48
+ FileUtils.rm_rf(CACHE_DIR)
49
+ end
49
50
 
50
- # @return [void]
51
- def self.init
52
- FileUtils.mkdir_p(CACHE_DIR)
53
- rescue
54
- # To prevent ~/ is not writable.
55
- send(:remove_const, :CACHE_DIR)
56
- const_set(:CACHE_DIR, File.join(HeapInfo::TMP_DIR, '.cache/heapinfo'))
57
- end
51
+ private
52
+
53
+ # @return [void]
54
+ def init
55
+ FileUtils.mkdir_p(CACHE_DIR)
56
+ rescue
57
+ # To prevent ~/ is not writable.
58
+ send(:remove_const, :CACHE_DIR)
59
+ const_set(:CACHE_DIR, File.join(HeapInfo::TMP_DIR, '.cache/heapinfo'))
60
+ FileUtils.mkdir_p(CACHE_DIR)
61
+ end
58
62
 
59
- # Clear the cache directory.
60
- # @return [void]
61
- def self.clear_all
62
- FileUtils.rm_rf CACHE_DIR
63
+ # @param [String] key
64
+ # @return [String] Prepend with {HeapInfo::Cache::CACHE_DIR}.
65
+ def realpath(key)
66
+ raise ArgumentError, 'Invalid key(file path)' if key =~ %r{[^\w/]}
67
+ File.join(CACHE_DIR, key)
68
+ end
63
69
  end
70
+
71
+ extend ClassMethods
64
72
  init
65
73
  end
66
74
  end
@@ -48,22 +48,21 @@ module HeapInfo
48
48
  #
49
49
  # Details are in {HeapInfo:Process#x}.
50
50
  # @param [Integer] count The number of result need to dump.
51
- # @param [Mixed] commands Same format as +#dump(*args)+.
52
- # @param [IO] io +IO+ that use for printing.
53
- # @return [NilClass] The return value of +io.puts+.
51
+ # @param [Symbol, String, Integer] address The base address to be dumped.
52
+ # @return [void]
54
53
  # @example
55
54
  # x 3, 0x400000
56
55
  # # 0x400000: 0x00010102464c457f 0x0000000000000000
57
56
  # # 0x400010: 0x00000001003e0002
58
- def x(count, *commands, io: $stdout)
59
- commands += [count * size_t]
57
+ def x(count, address)
58
+ commands = [address, count * size_t]
60
59
  base = base_of(*commands)
61
60
  res = dump(*commands).unpack(size_t == 4 ? 'L*' : 'Q*')
62
61
  str = res.group_by.with_index { |_, i| i / (16 / size_t) }.map do |round, values|
63
- format("%#x:\t", (base + round * 16)) +
62
+ Helper.hex(base + round * 16) + ":\t" +
64
63
  values.map { |v| Helper.color(format("0x%0#{size_t * 2}x", v)) }.join("\t")
65
64
  end.join("\n")
66
- io.puts str
65
+ puts str
67
66
  end
68
67
 
69
68
  # Search a specific value/string/regexp in memory.
@@ -92,57 +91,6 @@ module HeapInfo
92
91
  end
93
92
  end
94
93
 
95
- # Parse the dump command into +[base, offset, length]+
96
- # @param [Array] args The command, see examples for more information
97
- # @return [Array<Symbol, Integer>]
98
- # +[base, offset, length]+, while +base+ can be a +Symbol+ or an +Integer+.
99
- # +length+ has default value equals to +8+.
100
- # @example
101
- # HeapInfo::Dumper.parse_cmd([:heap, 32, 10])
102
- # # [:heap, 32, 10]
103
- # HeapInfo::Dumper.parse_cmd(['heap+0x10, 10'])
104
- # # [:heap, 16, 10]
105
- # HeapInfo::Dumper.parse_cmd(['heap+0x10'])
106
- # # [:heap, 16, 8]
107
- # HeapInfo::Dumper.parse_cmd([0x400000, 4])
108
- # # [0x400000, 0, 4]
109
- def self.parse_cmd(args)
110
- args = split_cmd args
111
- return :fail unless args.size == 3
112
- len = args[2].nil? ? DUMP_BYTES : Integer(args[2])
113
- offset = Integer(args[1])
114
- base = args[0]
115
- base = Helper.integer?(base) ? Integer(base) : base.delete(':').to_sym
116
- [base, offset, len]
117
- end
118
-
119
- # Helper for +#parse_cmd+.
120
- #
121
- # Split commands to exactly three parts: +[base, offset, length]+.
122
- # +length+ is +nil+ if not present.
123
- # @param [Array] args
124
- # @return [Array<String>] +[base, offset, length]+ in string expression.
125
- # @example
126
- # HeapInfo::Dumper.split_cmd([:heap, 32, 10])
127
- # # ['heap', '32', '10']
128
- # HeapInfo::Dumper.split_cmd([':heap+0x10, 10'])
129
- # # [':heap', '0x10', '10']
130
- # HeapInfo::Dumper.split_cmd([':heap+0x10'])
131
- # # [':heap', '0x10', nil]
132
- # HeapInfo::Dumper.split_cmd([0x400000, 4])
133
- # # ['4194304', 0, '4']
134
- def self.split_cmd(args)
135
- args = args.join(',').delete(' ').split(',').reject(&:empty?) # 'heap, 100', 32 => 'heap', '100', '32'
136
- return [] if args.empty?
137
- if args[0].include? '+' # 'heap+0x1'
138
- args.unshift(*args.shift.split('+', 2))
139
- elsif args.size <= 2 # no offset given
140
- args.insert(1, 0)
141
- end
142
- args << nil if args.size <= 2 # no length given
143
- args[0, 3]
144
- end
145
-
146
94
  private
147
95
 
148
96
  def need_permission
@@ -169,14 +117,28 @@ module HeapInfo
169
117
  File.open(@filename)
170
118
  end
171
119
 
172
- def base_len_of(*args)
173
- base, offset, len = Dumper.parse_cmd(args)
174
- if HeapInfo::ProcessInfo::EXPORT.include?(base) && (segment = @info.send(base)).is_a?(Segment)
175
- base = segment.base
176
- elsif !base.is_a?(Integer)
177
- raise ArgumentError, "Invalid base: #{base}" # invalid usage
178
- end
179
- [base + offset, len]
120
+ # Get the base address and length.
121
+ #
122
+ # @param [Integer, Symbol, String] arg The base address, see examples.
123
+ # @param [Integer] len An integer.
124
+ # @example
125
+ # base_len_of(123, 321) #=> [123, 321]
126
+ # base_len_of(123) #=> [123, DUMP_BYTES]
127
+ # base_len_of(:heap, 10) #=> [0x603000, 10] # assume heap base @ 0x603000
128
+ # base_len_of('heap+0x30', 10) #=> [0x603030, 10]
129
+ # base_len_of('elf+0x3*2-1') #=> [0x400005, DUMP_BYTES]
130
+ def base_len_of(arg, len = DUMP_BYTES)
131
+ values = HeapInfo::ProcessInfo::EXPORT.map do |seg|
132
+ segment = @info.respond_to?(seg) && @info.send(seg)
133
+ [seg, segment.base] if segment.is_a?(Segment)
134
+ end.compact.to_h
135
+ base = case arg
136
+ when Integer then arg
137
+ when Symbol then values[arg]
138
+ when String then Helper.evaluate(arg, store: values)
139
+ end
140
+ raise ArgumentError, "Invalid base: #{arg.inspect}" unless base.is_a?(Integer) # invalid usage
141
+ [base, len]
180
142
  end
181
143
 
182
144
  def base_of(*args)
@@ -37,8 +37,8 @@ module HeapInfo
37
37
  int_free_fast(av, ptr, size)
38
38
  elsif !chunk_is_mmapped(ptr) # Though this has been checked in #libc_free
39
39
  int_free_small(av, ptr, size)
40
- else
41
- munmap_chunk(ptr)
40
+ # else # never reached block, redundant code in glibc.
41
+ # munmap_chunk(ptr)
42
42
  end
43
43
  end
44
44
 
@@ -1,19 +1,9 @@
1
+ require 'dentaku'
2
+
1
3
  module HeapInfo
2
4
  # Some helper functions
3
5
  module Helper
4
- # Get the process id of a process
5
- # @param [String] prog The request process name
6
- # @return [Fixnum] process id
7
- def self.pidof(prog)
8
- # plz, don't cmd injection your self :p
9
- pid = `pidof #{prog}`.strip.to_i
10
- return nil if pid.zero? # process not exists yet
11
- throw "pidof #{prog} fail" unless pid.between?(2, 65_535)
12
- pid
13
- # TODO: handle when multi processes exists
14
- end
15
-
16
- # Create read <tt>/proc/[pid]/*</tt> methods
6
+ # Create read +/proc/[pid]/*+ methods
17
7
  %w(exe maps).each do |method|
18
8
  define_singleton_method("#{method}_of".to_sym) do |pid|
19
9
  begin
@@ -24,106 +14,152 @@ module HeapInfo
24
14
  end
25
15
  end
26
16
 
27
- # Parse the contents of <tt>/proc/[pid]/maps</tt>.
28
- #
29
- # @param [String] content The file content of <tt>/proc/[pid]/maps</tt>
30
- # @return [Array] In form of <tt>[[start, end, permission, name], ...]</tt>. See examples.
31
- # @example
32
- # HeapInfo::Helper.parse_maps(<<EOS
33
- # 00400000-0040b000 r-xp 00000000 ca:01 271708 /bin/cat
34
- # 00bc4000-00be5000 rw-p 00000000 00:00 0 [heap]
35
- # 7f2788315000-7f2788316000 r--p 00022000 ca:01 402319 /lib/x86_64-linux-gnu/ld-2.19.so
36
- # EOS
37
- # )
38
- # # [[0x400000, 0x40b000, 'r-xp', '/bin/cat'],
39
- # # [0xbc4000, 0xbe5000, 'rw-p', '[heap]'],
40
- # # [0x7f2788315000, 0x7f2788316000, 'r--p', '/lib/x86_64-linux-gnu/ld-2.19.so']]
41
- def self.parse_maps(content)
42
- lines = content.split("\n")
43
- lines.map do |line|
44
- s = line.scan(%r{^([0-9a-f]+)-([0-9a-f]+)\s([rwxp-]{4})[^/|\[]*([/|\[].+)$})[0]
45
- next nil if s.nil?
46
- s[0], s[1] = s[0, 2].map { |h| h.to_i(16) }
47
- s
48
- end.compact
49
- end
17
+ # Define class methods here.
18
+ module ClassMethods
19
+ # Get the process id of a process
20
+ # @param [String] prog The request process name
21
+ # @return [Fixnum] process id
22
+ def pidof(prog)
23
+ # plz, don't cmd injection your self :p
24
+ pid = `pidof #{prog}`.strip.to_i
25
+ return nil if pid.zero? # process not exists yet
26
+ throw "pidof #{prog} fail" unless pid.between?(2, 65_535)
27
+ pid
28
+ # TODO: handle when multi processes exists
29
+ end
50
30
 
51
- # Color codes for pretty print
52
- COLOR_CODE = {
53
- esc_m: "\e[0m",
54
- normal_s: "\e[38;5;1m", # red
55
- integer: "\e[38;5;12m", # light blue
56
- fatal: "\e[38;5;197m", # dark red
57
- bin: "\e[38;5;120m", # light green
58
- klass: "\e[38;5;155m", # pry like
59
- sym: "\e[38;5;229m", # pry like
60
- }.freeze
61
- # Wrapper color codes for pretty inspect.
62
- # @param [String] s Contents for wrapper
63
- # @param [Symbol?] sev Specific which kind of color want to use, valid symbols are defined in +#COLOR_CODE+.
64
- # If this argument is not present, will detect according to the content of +s+.
65
- # @return [String] wrapper with color codes.
66
- def self.color(s, sev: nil)
67
- s = s.to_s
68
- cc = COLOR_CODE
69
- color = if cc.key?(sev) then cc[sev]
70
- elsif s =~ /^(0x)?[0-9a-f]+$/ then cc[:integer] # integers
71
- else cc[:normal_s] # normal string
72
- end
73
- "#{color}#{s.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
74
- end
31
+ # Parse the contents of <tt>/proc/[pid]/maps</tt>.
32
+ #
33
+ # @param [String] content The file content of <tt>/proc/[pid]/maps</tt>
34
+ # @return [Array] In form of <tt>[[start, end, permission, name], ...]</tt>. See examples.
35
+ # @example
36
+ # HeapInfo::Helper.parse_maps(<<EOS
37
+ # 00400000-0040b000 r-xp 00000000 ca:01 271708 /bin/cat
38
+ # 00bc4000-00be5000 rw-p 00000000 00:00 0 [heap]
39
+ # 7f2788315000-7f2788316000 r--p 00022000 ca:01 402319 /lib/x86_64-linux-gnu/ld-2.19.so
40
+ # EOS
41
+ # )
42
+ # # [[0x400000, 0x40b000, 'r-xp', '/bin/cat'],
43
+ # # [0xbc4000, 0xbe5000, 'rw-p', '[heap]'],
44
+ # # [0x7f2788315000, 0x7f2788316000, 'r--p', '/lib/x86_64-linux-gnu/ld-2.19.so']]
45
+ def parse_maps(content)
46
+ lines = content.split("\n")
47
+ lines.map do |line|
48
+ s = line.scan(%r{^([0-9a-f]+)-([0-9a-f]+)\s([rwxp-]{4})[^/|\[]*([/|\[].+)$})[0]
49
+ next nil if s.nil?
50
+ s[0], s[1] = s[0, 2].map { |h| h.to_i(16) }
51
+ s
52
+ end.compact
53
+ end
75
54
 
76
- # Unpack strings to integer.
77
- #
78
- # Like the <tt>p32</tt> and <tt>p64</tt> in pwntools.
79
- #
80
- # @param [Integer] size_t Either 4 or 8.
81
- # @param [String] data String to be unpack.
82
- # @return [Integer] Unpacked result.
83
- # @example
84
- # HeapInfo::Helper.unpack(4, "\x12\x34\x56\x78")
85
- # # 0x78563412
86
- # HeapInfo::Helper.unpack(8, "\x12\x34\x56\x78\xfe\xeb\x90\x90")
87
- # # 0x9090ebfe78563412
88
- def self.unpack(size_t, data)
89
- data.unpack(size_t == 4 ? 'L*' : 'Q*')[0]
90
- end
55
+ # enable / disable the color function.
56
+ # @param [Boolean] on Enable or not.
57
+ # @return [void]
58
+ def toggle_color(on: false)
59
+ @disable_color = !on
60
+ end
91
61
 
92
- # Convert number in hex format
93
- #
94
- # @param [Integer] num Non-negative integer.
95
- # @return [String] number in hex format.
96
- # @example
97
- # HeapInfo::Helper.hex(1000) # => '0x3e8'
98
- def self.hex(num)
99
- '0x' + num.to_s(16)
100
- end
62
+ # Color codes for pretty print
63
+ COLOR_CODE = {
64
+ esc_m: "\e[0m",
65
+ normal_s: "\e[38;5;1m", # red
66
+ integer: "\e[38;5;12m", # light blue
67
+ fatal: "\e[38;5;197m", # dark red
68
+ bin: "\e[38;5;120m", # light green
69
+ klass: "\e[38;5;155m", # pry like
70
+ sym: "\e[38;5;229m", # pry like
71
+ }.freeze
72
+ # Wrapper color codes for pretty inspect.
73
+ # @param [String] s Contents for wrapper
74
+ # @param [Symbol?] sev Specific which kind of color want to use, valid symbols are defined in +#COLOR_CODE+.
75
+ # If this argument is not present, will detect according to the content of +s+.
76
+ # @return [String] wrapper with color codes.
77
+ def color(s, sev: nil)
78
+ s = s.to_s
79
+ return s if @disable_color
80
+ cc = COLOR_CODE
81
+ color = if cc.key?(sev) then cc[sev]
82
+ elsif integer?(s) then cc[:integer] # integers
83
+ else cc[:normal_s] # normal string
84
+ end
85
+ "#{color}#{s.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
86
+ end
101
87
 
102
- # Retrieve pure class name(without module) of an object
103
- # @param [Object] obj Any instance
104
- # @return [String] Class name of <tt>obj</tt>
105
- # @example
106
- # # suppose obj is an instance of HeapInfo::Chunk
107
- # Helper.class_name(obj)
108
- # # => 'Chunk'
109
- def self.class_name(obj)
110
- obj.class.name.split('::').last || obj.class.name
111
- end
88
+ # Unpack strings to integer.
89
+ #
90
+ # Like the <tt>p32</tt> and <tt>p64</tt> in pwntools.
91
+ #
92
+ # @param [Integer] size_t Either 4 or 8.
93
+ # @param [String] data String to be unpack.
94
+ # @return [Integer] Unpacked result.
95
+ # @example
96
+ # HeapInfo::Helper.unpack(4, "\x12\x34\x56\x78")
97
+ # # 0x78563412
98
+ # HeapInfo::Helper.unpack(8, "\x12\x34\x56\x78\xfe\xeb\x90\x90")
99
+ # # 0x9090ebfe78563412
100
+ def unpack(size_t, data)
101
+ data.unpack(size_t == 4 ? 'L*' : 'Q*')[0]
102
+ end
103
+
104
+ # Convert number in hex format
105
+ #
106
+ # @param [Integer] num Non-negative integer.
107
+ # @return [String] number in hex format.
108
+ # @example
109
+ # HeapInfo::Helper.hex(1000) # => '0x3e8'
110
+ def hex(num)
111
+ return format('0x%x', num) if num >= 0
112
+ format('-0x%x', -num)
113
+ end
114
+
115
+ # Retrieve pure class name(without module) of an object
116
+ # @param [Object] obj Any instance
117
+ # @return [String] Class name of <tt>obj</tt>
118
+ # @example
119
+ # # suppose obj is an instance of HeapInfo::Chunk
120
+ # Helper.class_name(obj)
121
+ # # => 'Chunk'
122
+ def class_name(obj)
123
+ obj.class.name.split('::').last || obj.class.name
124
+ end
125
+
126
+ # For checking a string is actually an integer.
127
+ # @param [String] str String to be checked.
128
+ # @return [Boolean] If +str+ can be converted into integer.
129
+ # @example
130
+ # Helper.integer? '1234'
131
+ # # => true
132
+ # Helper.integer? '0x1234'
133
+ # # => true
134
+ # Helper.integer? '0xheapoverflow'
135
+ # # => false
136
+ def integer?(str)
137
+ true if Integer(str)
138
+ rescue ArgumentError, TypeError
139
+ false
140
+ end
141
+
142
+ # Safe-eval using dentaku.
143
+ # @param [String] formula Formula to be eval.
144
+ # @param [Hash{Symbol => Integer}] store Predefined values.
145
+ # @return [Integer] Evaluate result.
146
+ def evaluate(formula, store: {})
147
+ calc = Dentaku::Calculator.new
148
+ formula = formula.delete(':')
149
+ formula.gsub!(/0x[\da-z]+/) do |s|
150
+ s.to_i(16).to_s
151
+ end
152
+ calc.store(store).evaluate(formula)
153
+ end
112
154
 
113
- # For checking a string is actually an integer.
114
- # @param [String] str String to be checked.
115
- # @return [Boolean] If +str+ can be converted into integer.
116
- # @example
117
- # Helper.integer? '1234'
118
- # # => true
119
- # Helper.integer? '0x1234'
120
- # # => true
121
- # Helper.integer? '0xheapoverflow'
122
- # # => false
123
- def self.integer?(str)
124
- true if Integer(str)
125
- rescue ArgumentError, TypeError
126
- false
155
+ # Get temp filename.
156
+ # @param [String] name Filename.
157
+ # @return [String] Temp filename.
158
+ def tempfile(name)
159
+ Dir::Tmpname.create(name, HeapInfo::TMP_DIR) {}
160
+ end
127
161
  end
162
+
163
+ extend ClassMethods
128
164
  end
129
165
  end
@@ -60,15 +60,17 @@ module HeapInfo
60
60
  end
61
61
 
62
62
  def resolve_main_arena_offset
63
- tmp_elf = HeapInfo::TMP_DIR + '/get_arena'
64
- libc_file = HeapInfo::TMP_DIR + '/libc.so.6'
65
- ld_file = HeapInfo::TMP_DIR + '/ld.so'
63
+ dir = HeapInfo::Helper.tempfile('')
64
+ FileUtils.mkdir(dir)
65
+ tmp_elf = File.join(dir, 'get_arena')
66
+ libc_file = File.join(dir, 'libc.so.6')
67
+ ld_file = File.join(dir, 'ld.so')
66
68
  flags = "-w #{size_t == 4 ? '-m32' : ''}"
67
69
  `cp #{name} #{libc_file} && \
68
70
  cp #{ld_name} #{ld_file} && \
69
71
  gcc #{flags} #{File.expand_path('../tools/get_arena.c', __FILE__)} -o #{tmp_elf} 2>&1 > /dev/null && \
70
- #{ld_file} --library-path #{HeapInfo::TMP_DIR} #{tmp_elf} && \
71
- rm #{tmp_elf} #{libc_file} #{ld_file}`.to_i(16)
72
+ #{ld_file} --library-path #{dir} #{tmp_elf} && \
73
+ rm -fr #{dir}`.to_i(16)
72
74
  end
73
75
  end
74
76
  end
@@ -2,18 +2,18 @@
2
2
  module HeapInfo
3
3
  # Main class of heapinfo.
4
4
  class Process
5
- # The default options of libaries,
6
- # use for matching glibc and ld segments in <tt>/proc/[pid]/maps</tt>
5
+ # The default options of libraries,
6
+ # use for matching glibc and ld segments in +/proc/[pid]/maps+.
7
7
  DEFAULT_LIB = {
8
8
  libc: /bc[^a-z]*\.so/,
9
9
  ld: %r{/ld-.+\.so}
10
10
  }.freeze
11
- # @return [Fixnum, NilClass] return the pid of process, <tt>nil</tt> if no such process found
11
+ # @return [Fixnum, NilClass] return the pid of process, +nil+ if no such process found
12
12
  attr_reader :pid
13
13
 
14
- # Instantiate a <tt>HeapInfo::Process</tt> object
15
- # @param [String, Fixnum] prog Process name or pid, see <tt>HeapInfo::heapinfo</tt> for more information
16
- # @param [Hash] options libraries' filename, see <tt>HeapInfo::heapinfo</tt> for more information
14
+ # Instantiate a {HeapInfo::Process} object.
15
+ # @param [String, Fixnum] prog Process name or pid, see {HeapInfo::heapinfo} for more information
16
+ # @param [Hash{Symbol => RegExp, String}] options libraries' filename, see {HeapInfo::heapinfo} for more information
17
17
  def initialize(prog, options = {})
18
18
  @prog = prog
19
19
  @options = DEFAULT_LIB.merge options
@@ -23,7 +23,7 @@ module HeapInfo
23
23
 
24
24
  # Reload a new process with same program name
25
25
  #
26
- # @return [HeapInfo::Process] return <tt>self</tt> for chainable.
26
+ # @return [HeapInfo::Process] return +self+ for chainable.
27
27
  # @example
28
28
  # puts h.reload!
29
29
  def reload!
@@ -34,9 +34,9 @@ module HeapInfo
34
34
 
35
35
  # Use this method to wrapper all HeapInfo methods.
36
36
  #
37
- # Since <tt>::HeapInfo</tt> is a tool(debugger) for local usage,
37
+ # Since {HeapInfo} is a tool(debugger) for local usage,
38
38
  # while exploiting remote service, all methods will not work properly.
39
- # So I suggest to wrapper all methods inside <tt>#debug</tt>,
39
+ # So I suggest to wrapper all methods inside {#debug},
40
40
  # which will ignore the block while the victim process is not found.
41
41
  #
42
42
  # @example
@@ -57,49 +57,77 @@ module HeapInfo
57
57
  # Note: This method require you have permission of attaching another process.
58
58
  # If not, a warning message will present.
59
59
  #
60
- # @param [Mixed] args Will be parsed into <tt>[base, offset, length]</tt>, see Examples for more information.
60
+ # @param [Mixed] args Will be parsed into +[base, length]+, see Examples for more information.
61
61
  # @return [String, HeapInfo::Nil]
62
62
  # The content needed. When the request address is not readable or the process not exists,
63
- # <tt>HeapInfo::Nil.new</tt> is returned.
63
+ # instance of {HeapInfo::Nil} is returned.
64
64
  #
65
65
  # @example
66
66
  # h = heapinfo('victim')
67
- # h.dump(:heap) # &heap[0, 8]
68
- # h.dump(:heap, 64) # &heap[0, 64]
69
- # h.dump(:heap, 256, 64) # &heap[256, 64]
70
- # h.dump('heap+256, 64' # &heap[256, 64]
71
- # h.dump('heap+0x100', 64) # &heap[256, 64]
67
+ # h.dump(:heap) # heap[0, 8]
68
+ # h.dump(:heap, 64) # heap[0, 64]
69
+ # h.dump('heap+256', 64) # heap[256, 64]
70
+ # h.dump('heap+0x100', 64) # heap[256, 64]
71
+ # h.dump('heap+0x100 * 2 + 0x300', 64) # heap[1024, 64]
72
72
  # h.dump(<segment>, 8) # semgent can be [heap, stack, (program|elf), libc, ld]
73
73
  # h.dump(addr, 64) # addr[0, 64]
74
74
  #
75
75
  # # Invalid usage
76
76
  # dump(:meow) # no such segment
77
- # dump('heap-1, 64') # not support '-'
78
77
  def dump(*args)
79
78
  return Nil.new unless load?
80
79
  dumper.dump(*args)
81
80
  end
82
81
 
83
82
  # Return the dump result as chunks.
84
- # see <tt>HeapInfo::Dumper#dump_chunks</tt> for more information.
83
+ # see {HeapInfo::Dumper#dump_chunks} for more information.
85
84
  #
86
85
  # @return [HeapInfo::Chunks, HeapInfo::Nil] An array of chunk(s).
87
- # @param [Mixed] args Same as arguments of <tt>#dump</tt>
86
+ # @param [Mixed] args Same as arguments of {#dump}.
88
87
  def dump_chunks(*args)
89
88
  return Nil.new unless load?
90
89
  dumper.dump_chunks(*args)
91
90
  end
92
91
 
92
+ # Show the offset in pretty way between the segment.
93
+ # Very useful in pwn when leak some address,
94
+ # see examples for more details.
95
+ # @param [Integer] addr The leaked address.
96
+ # @param [Symbol] sym
97
+ # The segement symbol to be calculated offset.
98
+ # If this parameter not given, will loop segments
99
+ # and find the most close one. See examples for more details.
100
+ # @return [void] Offset will show to stdout.
101
+ # @example
102
+ # h.offset(0x7f11f6ae1670, :libc)
103
+ # #=> 0xf6670 after libc
104
+ # h.offset(0x5559edc057a0, :heap)
105
+ # #=> 0x9637a0 after heap
106
+ # h.offset(0x7f11f6ae1670)
107
+ # #=> 0xf6670 after :libc
108
+ # h.offset(0x5559edc057a0)
109
+ # #=> 0x9637a0 after :heap
110
+ def offset(addr, sym = nil)
111
+ return unless load?
112
+ segment = @info.send(sym) if HeapInfo::ProcessInfo::EXPORT.include?(sym)
113
+ segment = nil unless segment.is_a?(HeapInfo::Segment)
114
+ if segment.nil?
115
+ sym, segment = @info.segments.select { |_, seg| seg.base <= addr }.min_by { |_, seg| addr - seg.base }
116
+ end
117
+ return puts "Invalid address #{Helper.hex(addr)}" if segment.nil?
118
+ puts Helper.color(Helper.hex(addr - segment.base)) + ' after ' + Helper.color(sym, sev: :sym)
119
+ end
120
+
93
121
  # Gdb-like command
94
122
  #
95
- # Show dump results like in gdb's command <tt>x</tt>,
96
- # while will auto detect the current elf class to decide using <tt>gx</tt> or <tt>wx</tt>.
123
+ # Show dump results like gdb's command +x+.
124
+ # While will auto detect the current elf class to decide using +gx+ or +wx+.
97
125
  #
98
- # The dump results wrapper with color codes and nice typesetting will output to <tt>stdout</tt> by default.
99
- # @param [Integer] count The number of result need to dump, see examples for more information
100
- # @param [Mixed] commands Same format as <tt>#dump(*args)</tt>, see <tt>#dump</tt> for more information
101
- # @param [IO] io <tt>IO</tt> that use for printing, default is <tt>$stdout</tt>
102
- # @return [NilClass] The return value of <tt>io.puts</tt>.
126
+ # The dump results wrapper with color codes and nice typesetting will output to +stdout+.
127
+ # @param [Integer] count The number of result need to dump, see examples for more information.
128
+ # @param [String, Symbol, Integer] address The base address to be dumped.
129
+ # Same format as {#dump(*args)}, see {#dump} for more information.
130
+ # @return [void]
103
131
  # @example
104
132
  # h.x 8, :heap
105
133
  # # 0x1f0d000: 0x0000000000000000 0x0000000000002011
@@ -110,23 +138,23 @@ module HeapInfo
110
138
  # h.x 3, 0x400000
111
139
  # # 0x400000: 0x00010102464c457f 0x0000000000000000
112
140
  # # 0x400010: 0x00000001003e0002
113
- def x(count, *commands, io: $stdout)
114
- return unless load? && io.respond_to?(:puts)
115
- dumper.x(count, *commands, io: io)
141
+ def x(count, address)
142
+ return unless load?
143
+ dumper.x(count, address)
116
144
  end
117
145
 
118
146
  # Gdb-like command.
119
147
  #
120
148
  # Search a specific value/string/regexp in memory.
121
149
  # @param [Integer, String, Regexp] pattern
122
- # The desired search pattern, can be value(<tt>Integer</tt>), string, or regular expression.
150
+ # The desired search pattern, can be value(+Integer+), string, or regular expression.
123
151
  # @param [Integer, String, Symbol] from
124
- # Start address for searching, can be segment(<tt>Symbol</tt>) or segments with offset.
152
+ # Start address for searching, can be segment(+Symbol+) or segments with offset.
125
153
  # See examples for more information.
126
154
  # @param [Integer] length
127
155
  # The search length limit, default is unlimited,
128
156
  # which will search until pattern found or reach unreadable memory.
129
- # @return [Integer, NilClass] The first matched address, <tt>nil</tt> is returned when no such pattern found.
157
+ # @return [Integer, NilClass] The first matched address, +nil+ is returned when no such pattern found.
130
158
  # @example
131
159
  # h.find(0xdeadbeef, 'heap+0x10', 0x1000)
132
160
  # # => 6299664 # 0x602010
@@ -147,15 +175,14 @@ module HeapInfo
147
175
 
148
176
  # Pretty dump of bins layouts.
149
177
  #
150
- # The request layouts will output to <tt>stdout</tt> by default.
178
+ # The request layouts will output to +stdout+.
151
179
  # @param [Array<Symbol>] args Bin type(s) you want to see.
152
- # @param [IO] io <tt>IO</tt> that use for printing, default is <tt>$stdout</tt>
153
- # @return [NilClass] The return value of <tt>io.puts</tt>.
180
+ # @return [void]
154
181
  # @example
155
- # h.layouts :fastbin, :unsorted_bin, :smallbin
156
- def layouts(*args, io: $stdout)
157
- return unless load? && io.respond_to?(:puts)
158
- io.puts libc.main_arena.layouts(*args)
182
+ # h.layouts(:fast, :unsorted, :small)
183
+ def layouts(*args)
184
+ return unless load?
185
+ puts libc.main_arena.layouts(*args)
159
186
  end
160
187
 
161
188
  # Show simple information of target process.
@@ -6,7 +6,7 @@ module HeapInfo
6
6
  class ProcessInfo
7
7
  # Methods to be transparent to +process+.
8
8
  # e.g. +process.libc+ alias to +process.info.libc+.
9
- EXPORT = %i(libc ld heap elf program stack bits).freeze
9
+ EXPORT = %i(libc ld heap program elf stack bits).freeze
10
10
 
11
11
  # @return [Integer] 32 or 64.
12
12
  attr_reader :bits
@@ -20,14 +20,14 @@ module HeapInfo
20
20
  attr_reader :ld
21
21
  alias elf program
22
22
 
23
- # Instantiate a {ProcessInfo} object
23
+ # Instantiate a {ProcessInfo} object.
24
24
  #
25
25
  # @param [HeapInfo::Process] process Load information from maps/memory for +process+.
26
26
  def initialize(process)
27
27
  @pid = process.pid
28
28
  options = process.instance_variable_get(:@options)
29
29
  maps!
30
- @bits = bits_of Helper.exe_of @pid
30
+ @bits = bits_of(Helper.exe_of(@pid))
31
31
  @elf = @program = Segment.find(maps, File.readlink("/proc/#{@pid}/exe"))
32
32
  @stack = Segment.find(maps, '[stack]')
33
33
  # well.. stack is a strange case because it will grow in runtime..
@@ -39,18 +39,27 @@ module HeapInfo
39
39
  # Heap will not be mmapped if the process not use heap yet, so create a lazy loading method.
40
40
  # Will re-read maps when heap segment not found yet.
41
41
  #
42
- # @return [HeapInfo::Segment] The {Segment} of heap
42
+ # @return [HeapInfo::Segment] The {Segment} of heap.
43
43
  def heap # special handle because heap might not be initialized in the beginning
44
44
  @heap ||= Segment.find(maps!, '[heap]')
45
45
  end
46
46
 
47
+ # Return segemnts load currently.
48
+ # @return [Hash{Symbol => Segment}] The segments in hash format.
49
+ def segments
50
+ EXPORT.map do |sym|
51
+ seg = send(sym)
52
+ [sym, seg] if seg.is_a?(Segment)
53
+ end.compact.to_h
54
+ end
55
+
47
56
  private
48
57
 
49
58
  attr_reader :maps
50
59
 
51
60
  # force reload maps
52
61
  def maps!
53
- @maps = Helper.parse_maps Helper.maps_of @pid
62
+ @maps = Helper.parse_maps(Helper.maps_of(@pid))
54
63
  end
55
64
 
56
65
  def bits_of(elf)
@@ -1,3 +1,3 @@
1
1
  module HeapInfo
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heapinfo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-14 00:00:00.000000000 Z
11
+ date: 2017-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dentaku
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement