fastlib 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. data/README.markdown +15 -0
  2. data/Rakefile +20 -0
  3. data/lib/fastlib.rb +312 -0
  4. metadata +72 -0
data/README.markdown ADDED
@@ -0,0 +1,15 @@
1
+ # FASTLIB
2
+
3
+ FASTLIB provides a method to encode large directories of libraries into a single archive file.
4
+ This is similar to capabilities like zip/ziprequire, except that it provides workarounds for
5
+ \_\_FILE\_\_ references, arbitrary compression, and arbitrary encoding of file and name contents.
6
+
7
+ # Usage
8
+
9
+ $ apt-get install fastlib
10
+ $ `gem env gemdir`/fastlib-0.0.1/lib/fastlib.rb dump lib/myarchive.fastlib lib/ lib/*
11
+ $ ruby -r rubygems -r fastlib -I lib/myarchive.fastlib application.rb
12
+
13
+
14
+ # Credits
15
+ Rapid7 LLC
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ task :build => :update do
4
+ Rake::Task['clean'].execute
5
+ puts "[*] Building fastlib.gemspec"
6
+ system "gem build fastlib.gemspec &> /dev/null"
7
+ end
8
+
9
+ task :release => :build do
10
+ puts "[*] Pushing fastlib to rubygems.org"
11
+ system "gem push fastlib-*.gem &> /dev/null"
12
+ Rake::Task['clean'].execute
13
+ end
14
+
15
+ task :clean do
16
+ system "rm -f *.gem >/dev/null 2>&1"
17
+ end
18
+
19
+ task :update do
20
+ end
data/lib/fastlib.rb ADDED
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # FASTLIB is a mechanism for loading large sets of libraries in a way that is
5
+ # faster and much more flexible than typical disk structures. FASTLIB includes
6
+ # hooks that can be used for both compression and encoding of Ruby libraries.
7
+ #
8
+
9
+ #
10
+ # This format was specifically created to improve the performance and
11
+ # AV-resistance of the Metasploit Framework and Rex libraries.
12
+ #
13
+
14
+
15
+ #
16
+ # This library is still in its early form; a large number of performance and
17
+ # compatiblity improvements are not yet included. Do not depend on the FASTLIB
18
+ # file format at this time.
19
+ #
20
+
21
+ require "find"
22
+
23
+ #
24
+ # Copyright (C) 2011 Rapid7. You can redistribute it and/or
25
+ # modify it under the terms of the ruby license.
26
+ #
27
+ #
28
+ # Roughly based on the rubyzip zip/ziprequire library:
29
+ # >> Copyright (C) 2002 Thomas Sondergaard
30
+ # >> rubyzip is free software; you can redistribute it and/or
31
+ # >> modify it under the terms of the ruby license.
32
+
33
+ module Kernel #:nodoc:all
34
+ alias :fastlib_original_require :require
35
+
36
+ #
37
+ # This method hooks the original Kernel.require to support
38
+ # loading files within FASTLIB archives
39
+ #
40
+ def require(name)
41
+ fastlib_require(name) || fastlib_original_require(name)
42
+ end
43
+
44
+ #
45
+ # This method handles the loading of FASTLIB archives
46
+ #
47
+ def fastlib_require(name)
48
+ name = name + ".rb" if not name =~ /\.rb$/
49
+ return false if fastlib_already_loaded?(name)
50
+ return false if fastlib_already_tried?(name)
51
+
52
+ # TODO: Implement relative path $: checks and adjust the
53
+ # search path within archives to match.
54
+
55
+ $:.grep( /^(.*)\.fastlib$/ ).each do |lib|
56
+ data = FastLib.load(lib, name)
57
+ next if not data
58
+ $" << name
59
+
60
+ # TODO: Implement a better stack trace that represents
61
+ # the original filename and line number.
62
+ Object.class_eval(data)
63
+ return true
64
+ end
65
+
66
+ $fastlib_miss << name
67
+
68
+ false
69
+ end
70
+
71
+ #
72
+ # This method determines whether the specific file name
73
+ # has already been loaded ($LOADED_FEATURES aka $")
74
+ #
75
+ def fastlib_already_loaded?(name)
76
+ re = Regexp.new("^" + Regexp.escape(name) + "$")
77
+ $".detect { |e| e =~ re } != nil
78
+ end
79
+
80
+ #
81
+ # This method determines whether the specific file name
82
+ # has already been attempted with the included FASTLIB
83
+ # archives.
84
+ #
85
+ # TODO: Ensure that this only applies to known FASTLIB
86
+ # archives and that newly included archives will
87
+ # be searched appropriately.
88
+ #
89
+ def fastlib_already_tried?(name)
90
+ $fastlib_miss ||= []
91
+ $fastlib_miss.include?(name)
92
+ end
93
+ end
94
+
95
+
96
+ #
97
+ # The FastLib class implements the meat of the FASTLIB archive format
98
+ #
99
+ class FastLib
100
+
101
+ VERSION = "0.0.1"
102
+
103
+ @@cache = {}
104
+
105
+ #
106
+ # This method returns the version of the fastlib library
107
+ #
108
+ def self.version
109
+ VERSION
110
+ end
111
+
112
+ #
113
+ # This method loads content from a specific archive file by name. If the
114
+ # noprocess argument is set to true, the contents will not be expanded to
115
+ # include workarounds for things such as __FILE__. This is useful when
116
+ # loading raw binary data where these strings may occur
117
+ #
118
+ def self.load(lib, name, noprocess=false)
119
+ data = ""
120
+ load_cache(lib)
121
+
122
+ return if not ( @@cache[lib] and @@cache[lib][name] )
123
+
124
+
125
+ ::File.open(lib, "rb") do |fd|
126
+ fd.seek(
127
+ @@cache[lib][:fastlib_header][0] +
128
+ @@cache[lib][:fastlib_header][1] +
129
+ @@cache[lib][name][0]
130
+ )
131
+ data = fastlib_filter( fd.read(@@cache[lib][name][1] ))
132
+ end
133
+
134
+ # Return the contents in raw or processed form
135
+ noprocess ? data : post_process(lib, name, data)
136
+ end
137
+
138
+ #
139
+ # This method caches the file list and offsets within the archive
140
+ #
141
+ def self.load_cache(lib)
142
+ return if @@cache[lib]
143
+ dict = {}
144
+ ::File.open(lib, 'rb') do |fd|
145
+ head = fd.read(4)
146
+ return if head != "FAST"
147
+ hlen = fd.read(4).unpack("N")[0]
148
+ dict[:fastlib_header] = [8, hlen]
149
+
150
+ nlen, doff, dlen = fd.read(12).unpack("N*")
151
+
152
+ while nlen > 0
153
+ name = fastlib_filter_name( fd.read(nlen) )
154
+ dict[name] = [doff, dlen]
155
+
156
+ nlen, doff, dlen = fd.read(12).unpack("N*")
157
+ end
158
+ @@cache[lib] = dict
159
+ end
160
+ end
161
+
162
+ #
163
+ # This method provides a way to hook the translation of file names
164
+ # from the dictionary in the file to the final string. This can be
165
+ # used to provide encryption or compression.
166
+ #
167
+ def self.fastlib_filter_name(name)
168
+ name
169
+ end
170
+
171
+ #
172
+ # This method provides a way to hook the translation of file content
173
+ # from the archive to the final content. This can be used to provide
174
+ # encryption or compression.
175
+ #
176
+ def self.fastlib_filter(data)
177
+ data
178
+ end
179
+
180
+ #
181
+ # This method provides a way to create a FASTLIB archive programatically,
182
+ # the key arguments are the name of the destination archive, the base
183
+ # directory that should be excluded from the archived path, and finally
184
+ # the list of specific files and directories to include in the archive.
185
+ #
186
+ def self.dump(lib, bdir, *dirs)
187
+ head = ""
188
+ data = ""
189
+ hidx = 0
190
+ didx = 0
191
+
192
+ bdir = bdir.gsub(/\/$/, '')
193
+ brex = /^#{Regexp.escape(bdir)}\//
194
+
195
+ dirs.each do |dir|
196
+ ::Find.find(dir).each do |path|
197
+ next if not ::File.file?(path)
198
+ name = fastlib_filter_name( path.sub( brex, "" ) )
199
+ buff = ""
200
+ ::File.open(path, "rb") do |fd|
201
+ buff = fd.read(fd.stat.size)
202
+ end
203
+
204
+ head << [ name.length, didx, buff.length ].pack("NNN")
205
+ head << name
206
+ hidx = hidx + 12 + name.length
207
+
208
+ data << fastlib_filter( buff )
209
+ didx = didx + buff.length
210
+ end
211
+ end
212
+
213
+ head << [0,0,0].pack("NNN")
214
+
215
+ ::File.open(lib, "wb") do |fd|
216
+ fd.write("FAST")
217
+ fd.write( [ head.length ].pack("N") )
218
+ fd.write( head )
219
+ fd.write( data )
220
+ end
221
+ end
222
+
223
+ #
224
+ # This archive provides a way to list the contents of an archive
225
+ # file, returning the names only in sorted order.
226
+ #
227
+ def self.list(lib)
228
+ load_cache(lib)
229
+ ( @@cache[lib] || {} ).keys.map{|x| x.to_s }.sort
230
+ end
231
+
232
+ # This method is called on the loaded
233
+
234
+ is required to expand __FILE__ and other inline
235
+ # dynamic constants to map to the correct location
236
+ def self.post_process(lib, name, data)
237
+ data.gsub('__FILE__', "'#{ ::File.expand_path(::File.join(::File.dirname(lib), name)) }'")
238
+ end
239
+
240
+ end
241
+
242
+
243
+ #
244
+ # Allow this library to be used as an executable to create and list
245
+ # FASTLIB archives
246
+ #
247
+ if __FILE__ == $0
248
+ cmd = ARGV.shift
249
+ unless ["dump", "list", "version"].include?(cmd)
250
+ $stderr.puts "Usage: #{$0} [dump|list|version] <arguments>"
251
+ exit(0)
252
+ end
253
+
254
+ case cmd
255
+ when "dump"
256
+ dst = ARGV.shift
257
+ dir = ARGV.shift
258
+ src = ARGV
259
+ unless dst and dir and src.length > 0
260
+ $stderr.puts "Usage: #{$0} dump destination.fastlib base_dir src1 src2 ... src99"
261
+ exit(0)
262
+ end
263
+ FastLib.dump(dst, dir, *src)
264
+
265
+ when "list"
266
+ src = ARGV.shift
267
+ unless src
268
+ $stderr.puts "Usage: #{$0} list src_lib "
269
+ exit(0)
270
+ end
271
+ $stdout.puts "Library: #{src}"
272
+ $stdout.puts "====================================================="
273
+ FastLib.list(src).each do |name|
274
+ $stdout.puts " - #{name}"
275
+ end
276
+ $stdout.puts ""
277
+
278
+ when "version"
279
+ $stdout.puts "FastLib Version #{FastLib.version}"
280
+ end
281
+
282
+ exit(0)
283
+ end
284
+
285
+ #
286
+ # FASTLIB archive format (subject to change without notice)
287
+ #
288
+ =begin
289
+
290
+ * All integers are 32-bit and in network byte order (big endian / BE)
291
+ * The file signature is 0x46415354 (big endian, use htonl() if necessary)
292
+ * The header is always 8 bytes into the archive (magic + header length)
293
+ * The data section is always 8 + header length into the archive
294
+ * The header entries always start with 'fastlib_header'
295
+ * The header entries always consist of 12 bytes + name length (no alignment)
296
+ * The header name data may be encoded, compressed, or transformed
297
+ * The data entries may be encoded, compressed, or transformed too
298
+
299
+
300
+ 4 bytes: "FAST"
301
+ 4 bytes: NBO header length
302
+ [
303
+ 4 bytes: name length (0 = End of Names)
304
+ 4 bytes: data offset
305
+ 4 bytes: data length
306
+ ]
307
+ [ Raw Data ]
308
+
309
+ =end
310
+
311
+
312
+
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlib
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - HD Moore
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-20 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: This gem provides a way to load libraries from an archive
23
+ email:
24
+ - hdm@metasploit.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.markdown
31
+ files:
32
+ - README.markdown
33
+ - Rakefile
34
+ - lib/fastlib.rb
35
+ has_rdoc: true
36
+ homepage: https://github.com/rapid7/fastlib
37
+ licenses:
38
+ - BSD
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ hash: 57
50
+ segments:
51
+ - 1
52
+ - 8
53
+ - 7
54
+ version: 1.8.7
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.4.2
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: FASTLIB archive library
71
+ test_files: []
72
+