]");
- } else {
- // Any other case, attempt to send the jar
- String host = args[0];
- String port = args.length > 1 ? args[1] : "9025";
-
- Path jarPath = discoverJar();
- if (jarPath == null) {
- throw new FileNotFoundException("Jar file path could not be determined from the classloader");
- }
+ protected void sendJar(String[] args) throws Exception {
+ // Any other case, attempt to send the jar
+ String host = args[0];
+ String port = args.length > 1 ? args[1] : "9025";
- sendJar(host, port, jarPath);
+ Path jarPath = JarUtils.discoverJar(null);
+ if (jarPath == null) {
+ throw new FileNotFoundException("Jar file path could not be determined from the classloader");
}
+
+ JarUtils.sendJar(host, port, jarPath);
}
}
diff --git a/xploit/src/main/java/org/ps5jb/client/JarUtils.java b/xploit/src/main/java/org/ps5jb/client/JarUtils.java
new file mode 100644
index 0000000..be0b64c
--- /dev/null
+++ b/xploit/src/main/java/org/ps5jb/client/JarUtils.java
@@ -0,0 +1,105 @@
+package org.ps5jb.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PrivilegedActionException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import jdk.internal.loader.URLClassPath;
+import org.ps5jb.sdk.core.OpenModuleAction;
+
+/**
+ * Utility functions to determine the path of the JAR file on disc and to send the JAR file to a remote loader.
+ */
+public class JarUtils {
+ /**
+ * Sends the jar to the remote host.
+ *
+ * @param hostArg Hostname or IP address of the machine receiving the JAR.
+ * @param portArg Port on which the machine is listening for the connection.
+ * @param jarPath Path to the JAR file to send.
+ * @throws IOException If I/O exception occurs during sending.
+ */
+ public static void sendJar(String hostArg, String portArg, Path jarPath) throws IOException {
+ InetAddress addr = InetAddress.getByName(hostArg);
+ int port = Integer.parseInt(portArg);
+
+ Socket socket = new Socket(addr, port);
+ socket.setSoTimeout(5000);
+
+ InputStream jarStream = Files.newInputStream(jarPath);
+ try {
+ OutputStream out = socket.getOutputStream();
+ try {
+ byte[] buf = new byte[8192];
+ int readCount;
+ while ((readCount = jarStream.read(buf)) != -1) {
+ out.write(buf, 0, readCount);
+ }
+
+ System.out.println("JAR successfully sent");
+ } finally {
+ out.close();
+ }
+ } finally {
+ jarStream.close();
+ }
+ }
+
+ /**
+ * Uses the class classloader and reflection to determine the path to own JAR file.
+ *
+ * @param classLoader Classloader used to load the JAR file. If null, the classloader of this class will be used.
+ * @return Path to the JAR file containing this class. Returns {@code null} if JAR path could not be determined.
+ * @throws NoSuchFieldException If the class classloader does not have "ucp" field. The classloader is assumed to
+ * be a subclass of {@link jdk.internal.loader.BuiltinClassLoader}.
+ * @throws IllegalAccessException If access to the classloader state is denied through reflection.
+ * @throws IOException If I/O error occurs while reading the JAR file.
+ * @throws PrivilegedActionException If access to the classloader module is denied through reflection.
+ */
+ public static Path discoverJar(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException, IOException, PrivilegedActionException {
+ Path jarPath = null;
+
+ // Make sure we have access to the needed internal JDK class
+ OpenModuleAction.execute("jdk.internal.loader.URLClassPath");
+
+ // Try to determine the JAR file from classloader
+ if (classLoader == null) {
+ classLoader = JarUtils.class.getClassLoader();
+ }
+ Field field = classLoader.getClass().getDeclaredField("ucp");
+ field.setAccessible(true);
+ URLClassPath ucp = (URLClassPath) field.get(classLoader);
+ for (URL url : ucp.getURLs()) {
+ if (url.getProtocol().equals("file")) {
+ Path path;
+ try {
+ path = Path.of(new URI(url.toString()));
+ } catch (URISyntaxException e) {
+ // Should not happen in practice
+ throw new RuntimeException(e.getClass().getName() + ": " + e.getMessage());
+ }
+ JarFile jar = new JarFile(path.toFile());
+ String classEntryName = JarMain.class.getName().replace('.', '/') + ".class";
+ JarEntry jarEntry = jar.getJarEntry(classEntryName);
+ if (jarEntry != null) {
+ jarPath = path;
+ break;
+ }
+ }
+ }
+
+ return jarPath;
+ }
+}
diff --git a/xploit/src/main/java/org/ps5jb/client/payloads/DumpClasses.java b/xploit/src/main/java/org/ps5jb/client/payloads/DumpClasses.java
new file mode 100644
index 0000000..9b43145
--- /dev/null
+++ b/xploit/src/main/java/org/ps5jb/client/payloads/DumpClasses.java
@@ -0,0 +1,409 @@
+package org.ps5jb.client.payloads;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.module.ModuleReader;
+import java.lang.module.ModuleReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.Socket;
+import java.security.PrivilegedActionException;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.ps5jb.loader.SocketListener;
+import org.ps5jb.loader.Status;
+import org.ps5jb.sdk.core.OpenModuleAction;
+
+/**
+ *
+ * This sample creates a zip archive in the temp directory
+ * with all the known classes in the current thread's classloader.
+ *
+ *
+ * To use, simply send this class to the PS5 for execution in the JAR Loader.
+ * Then use nc or another tool to connect to the PS5:
+ * nc [PS5 IP] 9125 > classpath.zip
+ *
+ *
+ * Upon connection, the classpath will be dumped and sent back to `nc`.
+ * Depending on OS `nc` may not terminate by itself. When PS5 reports that
+ * the dump is finished, simply terminate it by force.
+ *
+ */
+public class DumpClasses extends SocketListener {
+ /**
+ * Default constructor. Listens on port 9125.
+ *
+ * @throws IOException If listening socket could not be created.
+ */
+ public DumpClasses() throws IOException {
+ this(9125);
+ }
+
+ /**
+ * Constructor for creating a dump listener on a custom port.
+ *
+ * @param port Port number to listen on.
+ * @throws IOException If listening socket could not be created.
+ */
+ public DumpClasses(int port) throws IOException {
+ super("Classpath Dumper", port);
+ }
+
+ /**
+ * Executes the classpath dump of the current thread's classloader.
+ *
+ * @return Created zip file.
+ * @throws Exception Re-throw any exception.
+ */
+ public File dumpClasspath() throws Exception {
+ File dumpZip = File.createTempFile("classpath", ".zip");
+ dumpZip.deleteOnExit();
+ Status.println("Dumping class path to: " + dumpZip.getAbsolutePath());
+
+ ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dumpZip));
+ try {
+ // Dump current thread classloader, make sure to iterate over all class hierarchy
+ Set dumpedEntries = new HashSet();
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ ClassLoader prevCl = null;
+ while (cl != null && !cl.equals(prevCl)) {
+ prevCl = cl;
+
+ dumpClassLoader(cl, zip, dumpedEntries);
+ cl = cl.getParent();
+ }
+
+ // Dump bdj specific jars. They are at fixed location.
+ // These seem to be added to boot class loader,
+ // not sure yet how to get to them using generic dumper above.
+ File pbpJar = new File("/app0/cdc/lib/pbp.jar");
+ if (pbpJar.exists()) {
+ Status.println("Dumping " + pbpJar);
+ dumpJarFile(new JarFile(pbpJar), zip, new HashSet());
+ }
+ File bdjstackJar = new File("/app0/cdc/bdjstack.jar");
+ if (bdjstackJar.exists()) {
+ Status.println("Dumping " + bdjstackJar);
+ dumpJarFile(new JarFile(bdjstackJar), zip, new HashSet());
+ }
+
+ zip.finish();
+ } finally {
+ zip.close();
+ }
+
+ return dumpZip;
+ }
+
+ /**
+ * Dumps the classes of a given class loader to a zip stream
+ *
+ * @param cl Classloader to dump
+ * @param zip Zip output stream where class files will be dumped.
+ * @param dumpedEntries Set which will be updated with all the dumped entries. If there are duplicates, then
+ * only the first one will be dumped.
+ * @throws Exception Re-throw any exception.
+ */
+ protected void dumpClassLoader(ClassLoader cl, ZipOutputStream zip, Set dumpedEntries) throws Exception {
+ Class prevClass = null;
+ Class c = cl.getClass();
+ Status.println("Analysis of the class loader " + cl);
+ while (c != null && !c.equals(prevClass)) {
+ prevClass = c;
+ Status.println(" Analysis of the class " + c.getName());
+
+ // Attempt to dump using built-in method. If does not work, try JDK ucp method.
+ // Stop hierarchy traversal when something is dumped.
+ if (!tryDumpBuiltinClassLoader(cl, c, zip, dumpedEntries)) {
+ if (!tryDumpJdkUcpClassLoader(cl, c, zip, dumpedEntries)) {
+ c = c.getSuperclass();
+ }
+ }
+ }
+ }
+
+ /**
+ * Use reflection to traverse all the resources of the given class loader and dump them
+ * to a zip output stream.
+ *
+ * @param cl Class loader that is expected to have ucp
field containing URLs of the JARs
+ * owned by this class loader.
+ * @param clClass Class on which to perform the reflection operations. Must be the class of the class loader
+ * or one of its superclasses.
+ * @param zip Zip output stream where the found resources will be written.
+ * @param dumpedEntries Set which will be updated with all the dumped entries. If there are duplicates, then
+ * only the first one will be dumped.
+ * @return True if dumping was successful; false if the specified class loader cannot be dumped.
+ * @throws Exception Any exception occurred during dumping is re-thrown.
+ */
+ protected boolean tryDumpJdkUcpClassLoader(ClassLoader cl, Class clClass, ZipOutputStream zip, Set dumpedEntries) throws Exception {
+ try {
+ OpenModuleAction.execute("jdk.internal.loader.URLClassPath");
+
+ Field ucpField = clClass.getDeclaredField("ucp");
+ ucpField.setAccessible(true);
+
+ Object ucpObject = ucpField.get(cl);
+ if (ucpObject != null && (ucpObject instanceof jdk.internal.loader.URLClassPath)) {
+ jdk.internal.loader.URLClassPath ucp = (jdk.internal.loader.URLClassPath) ucpObject;
+
+ Method getLoaderMethod = ucp.getClass().getDeclaredMethod("getLoader", new Class[] { int.class });
+ getLoaderMethod.setAccessible(true);
+
+ Object loader = null;
+ int i;
+ for (i = 0; (loader = getLoaderMethod.invoke(ucp, new Object[] { new Integer(i) })) != null; ++i) {
+ Field jarfileField = loader.getClass().getDeclaredField("jarfile");
+ jarfileField.setAccessible(true);
+ JarFile jarFile = (JarFile) jarfileField.get(loader);
+ if (jarFile != null) {
+ Status.println(" Dumping " + jarFile.getName());
+ try {
+ dumpJarFile(jarFile, zip, dumpedEntries);
+ } catch (IOException e) {
+ Status.printStackTrace("Skipping due to error", e);
+ }
+ } else {
+ Status.println(" Skipping loader " + loader + " since it does not have a JAR file");
+ }
+ }
+
+ return true;
+ }
+ } catch (NoSuchFieldException | PrivilegedActionException e) {
+ // Ignore, not a `ucp` class loader
+ }
+
+ return false;
+ }
+
+ /**
+ * Use reflection to traverse all the resources of the given class loader and dump them
+ * to a zip output stream.
+ *
+ * @param cl Class loader that is expected to be a descendant of {@link jdk.internal.loader.BuiltinClassLoader}.
+ * @param clClass Class on which to perform the reflection operations. Must be the class of the class loader
+ * or one of its superclasses.
+ * @param zip Zip output stream where the found resources will be written.
+ * @param dumpedEntries Set which will be updated with all the dumped entries. If there are duplicates, then
+ * only the first one will be dumped.
+ * @return True if dumping was successful; false if the specified class loader cannot be dumped.
+ * @throws Exception Any exception occurred during dumping is re-thrown.
+ */
+ protected boolean tryDumpBuiltinClassLoader(ClassLoader cl, Class clClass, ZipOutputStream zip, Set dumpedEntries) throws Exception {
+ try {
+ OpenModuleAction.execute("java.lang.module.ModuleReference");
+ OpenModuleAction.execute("jdk.internal.module.SystemModuleFinders");
+
+ // On PS5, stream API is put away in jdk.internal.utl package.
+ // Because of this, all the calls to iterate over modules below have to use reflection.
+ try {
+ OpenModuleAction.execute("jdk.internal.util.Optional");
+ OpenModuleAction.execute("jdk.internal.util.stream.Stream");
+ } catch (PrivilegedActionException e) {
+ Status.println("Error while opening PS5-specific jdk.internal.util package. " +
+ "Assuming this package does not exist in the current execution environment. " +
+ "Error: " + e.getException().getClass() + "; " +
+ "Message: " + e.getException().getMessage());
+ }
+
+ Field ptmField = clClass.getDeclaredField("packageToModule");
+ ptmField.setAccessible(true);
+ Map ptm = (Map) ptmField.get(cl);
+
+ Set processed = new HashSet();
+ Iterator modules = ptm.values().iterator();
+ while (modules.hasNext()) {
+ Object loadedModule = modules.next();
+ if (!processed.contains(loadedModule)) {
+ Field mrefField = loadedModule.getClass().getDeclaredField("mref");
+ mrefField.setAccessible(true);
+ ModuleReference mref = (ModuleReference) mrefField.get(loadedModule);
+
+ Status.println(" Dumping " + mref.descriptor().name());
+
+ final ModuleReader mr = mref.open();
+ try {
+ Method listMethod = mr.getClass().getMethod("list", new Class[0]);
+ listMethod.setAccessible(true);
+
+ Method openMethod = mr.getClass().getMethod("open", new Class[] { String.class });
+ openMethod.setAccessible(true);
+
+ Object resourceStream = listMethod.invoke(mr, new Object[0]);
+ Method toArrayMethod = resourceStream.getClass().getMethod("toArray", new Class[0]);
+ toArrayMethod.setAccessible(true);
+
+ Object[] resources = (Object[]) toArrayMethod.invoke(resourceStream, new Object[0]);
+ for (Object res : resources) {
+ String resName = mref.descriptor().name() + "/" + res;
+ if (!dumpedEntries.contains(resName)) {
+ Object isOptional = openMethod.invoke(mr, new Object[] { res });
+
+ Method isPresentMethod = isOptional.getClass().getMethod("isPresent", new Class[0]);
+ isPresentMethod.setAccessible(true);
+ Method getMethod = isOptional.getClass().getMethod("get", new Class[0]);
+ getMethod.setAccessible(true);
+
+ if (((Boolean) isPresentMethod.invoke(isOptional, new Object[0])).booleanValue()) {
+ InputStream is = (InputStream) getMethod.invoke(isOptional, new Object[0]);
+ try {
+ createZipEntry(is, resName, zip);
+ dumpedEntries.add(resName);
+ } finally {
+ is.close();
+ }
+ } else {
+ Status.println(" Failed to open classpath resource: " + res);
+ }
+ }
+ }
+ } finally {
+ mr.close();
+ }
+
+ processed.add(loadedModule);
+ }
+ }
+
+ return true;
+ } catch (NoSuchFieldException e) {
+ // Ignore, not a built-in class loader.
+ }
+
+ return false;
+ }
+
+ /**
+ * Dumps a JarFile to the zip output stream.
+ *
+ * @param jarFile Jar file to dump.
+ * @param zip Zip output stream where Jar file will be dumped.
+ * @param dumpedEntries Set which will be updated with all the dumped entries. If there are duplicates, then
+ * only the first one will be dumped.
+ * @throws IOException Any exception occurred during dumping is re-thrown.
+ */
+ protected void dumpJarFile(JarFile jarFile, ZipOutputStream zip, Set dumpedEntries) throws IOException {
+ Enumeration jarEntries = jarFile.entries();
+ while (jarEntries.hasMoreElements()) {
+ JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
+ if (!jarEntry.isDirectory()) {
+ File urlPath = new File(jarFile.getName());
+ String resName = urlPath.getName() + "/" + jarEntry.getName();
+ if (!dumpedEntries.contains(resName)) {
+ InputStream is = jarFile.getInputStream(jarEntry);
+ try {
+ createZipEntry(is, resName, zip);
+ dumpedEntries.add(resName);
+ } finally {
+ is.close();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a new zip entry with the contents of the given input stream in the specified output stream.
+ *
+ * @param is Input stream containing data for the new zip entry.
+ * @param entryName File name inside the zip.
+ * @param zip Zip output stream.
+ * @throws IOException If I/O error occurs.
+ */
+ protected void createZipEntry(InputStream is, String entryName, ZipOutputStream zip) throws IOException {
+ ZipEntry zipEntry = new ZipEntry(entryName);
+ zip.putNextEntry(zipEntry);
+ try {
+ writeResource(is, zip);
+ } finally {
+ zip.closeEntry();
+ }
+ }
+
+ /**
+ * Read the given resource from the input stream and write it to the target output stream.
+ *
+ * @param is Input stream to read the resource from.
+ * @param out Output stream where the resource will be copied.
+ * @throws IOException If I/O error occurs.
+ */
+ protected void writeResource(InputStream is, OutputStream out) throws IOException {
+ if (is != null) {
+ try {
+ byte[] buf = new byte[8 * 1024];
+ int r;
+ while ((r = is.read(buf)) > 0) {
+ out.write(buf, 0, r);
+ }
+ } finally {
+ is.close();
+ }
+ }
+ }
+
+ /**
+ * Dump the classpath and send it back to the client over network.
+ *
+ * @param clientSocket Socket where the dumped classpath will be sent.
+ * @throws Exception Any exception thrown during the execution.
+ */
+ @Override
+ public void acceptClient(Socket clientSocket) throws Exception {
+ File dumpZip = dumpClasspath();
+
+ Status.println("Sending the dump to: " + clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort());
+ try {
+ OutputStream out = clientSocket.getOutputStream();
+ try {
+ InputStream is = new FileInputStream(dumpZip);
+ try {
+ writeResource(is, out);
+ } finally {
+ is.close();
+ }
+ } finally {
+ out.close();
+ }
+ Status.println("Classpath dump sent successfully");
+
+ // Default implementation of SocketListener listens infinitely.
+ // We terminate the listener after the dump is complete.
+ terminate();
+ } finally {
+ if (!dumpZip.delete()) {
+ Status.println("Failed to delete the temporary classpath dump");
+ }
+ }
+ }
+
+ /**
+ * Handle any exception during dumping by terminating the listener thread.
+ *
+ * @param ex Exception to handle.
+ */
+ @Override
+ public void handleException(Throwable ex) {
+ super.handleException(ex);
+
+ // Terminate dumper on error
+ try {
+ terminate();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/xploit/src/main/java/org/ps5jb/client/payloads/ListDirEnts.java b/xploit/src/main/java/org/ps5jb/client/payloads/ListDirEnts.java
new file mode 100644
index 0000000..08d84b9
--- /dev/null
+++ b/xploit/src/main/java/org/ps5jb/client/payloads/ListDirEnts.java
@@ -0,0 +1,79 @@
+package org.ps5jb.client.payloads;
+
+import org.ps5jb.loader.Status;
+import org.ps5jb.sdk.core.Pointer;
+import org.ps5jb.sdk.core.SdkException;
+import org.ps5jb.sdk.include.sys.FCntl;
+import org.ps5jb.sdk.include.sys.dirent.DirEnt;
+import org.ps5jb.sdk.include.sys.dirent.DirType;
+import org.ps5jb.sdk.include.sys.fcntl.OpenFlag;
+import org.ps5jb.sdk.lib.LibKernel;
+
+/**
+ * This sample uses native code execution to list accessible entries starting from root directory.
+ * Since the PS5 screen output is rather small, it's advisable to properly configure
+ * the JAR loader with a {@link org.ps5jb.loader.RemoteLogger remote logger} which would
+ * allow to capture all the textual screen output over network.
+ */
+public class ListDirEnts implements Runnable {
+ /**
+ * Use native calls from "dirent" module to list the available filesystem entries.
+ */
+ public void run() {
+ try {
+ LibKernel libKernel = new LibKernel();
+ FCntl fcntl = new FCntl(libKernel);
+ try {
+ getDirEntries("/", "", libKernel);
+ } finally {
+ libKernel.closeLibrary();
+ }
+ } catch (SdkException e) {
+ Status.printStackTrace(e.getMessage(), e);
+ }
+ }
+
+ private void getDirEntries(String path, String indent, LibKernel libKernel) throws SdkException {
+ FCntl fcntl = new FCntl(libKernel);
+ if (libKernel.sceKernelCheckReachability(path) == 0) {
+ int fd = fcntl.open(path, OpenFlag.O_RDONLY, OpenFlag.O_DIRECTORY);
+ try {
+ int BUF_SIZE = 16 * 1024;
+ int remainingSize = BUF_SIZE;
+ Pointer db = Pointer.malloc(BUF_SIZE);
+ try {
+ DirEnt dirEnt = null;
+ remainingSize = libKernel.getdents(fd, db, BUF_SIZE);
+ while (remainingSize > 0 && remainingSize <= BUF_SIZE) {
+ if (dirEnt == null) {
+ dirEnt = new DirEnt(db);
+ }
+ if (!dirEnt.getName().equals(".") && !dirEnt.getName().equals("..")) {
+ String childPath = path + (path == "/" ? "" : "/") + dirEnt.getName();
+ Status.println(indent + dirEnt.getName() + " [" + dirEnt.getDirType() + "]");
+ if (dirEnt.getDirType().equals(DirType.DT_DIR)) {
+ try {
+ getDirEntries(childPath, indent + " ", libKernel);
+ } catch (SdkException e) {
+ Status.println(indent + " [ERROR] " + e.getMessage());
+ }
+ }
+ }
+
+ long oldAddr = dirEnt.getPointer().addr();
+ dirEnt = dirEnt.next(remainingSize);
+ if (dirEnt == null) {
+ remainingSize = libKernel.getdents(fd, db, BUF_SIZE);
+ } else {
+ remainingSize -= dirEnt.getPointer().addr() - oldAddr;
+ }
+ }
+ } finally {
+ db.free();
+ }
+ } finally {
+ fcntl.close(fd);
+ }
+ }
+ }
+}
diff --git a/xploit/src/main/java/org/ps5jb/client/payloads/PrintSystemProperties.java b/xploit/src/main/java/org/ps5jb/client/payloads/PrintSystemProperties.java
new file mode 100644
index 0000000..c03224e
--- /dev/null
+++ b/xploit/src/main/java/org/ps5jb/client/payloads/PrintSystemProperties.java
@@ -0,0 +1,35 @@
+package org.ps5jb.client.payloads;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.TreeSet;
+
+import org.ps5jb.loader.Status;
+
+/**
+ * This simple payload outputs the values of all the system properties.
+ */
+public class PrintSystemProperties implements Runnable {
+ /**
+ * Print all system properties sorted alphabetically by property name.
+ */
+ @Override
+ public void run() {
+ Properties props = System.getProperties();
+
+ Enumeration propNames = props.propertyNames();
+ TreeSet sortedPropNames = new TreeSet();
+ while (propNames.hasMoreElements()) {
+ String propName = (String) propNames.nextElement();
+ sortedPropNames.add(propName);
+ }
+
+ Iterator sortedPropIter = sortedPropNames.iterator();
+ while (sortedPropIter.hasNext()) {
+ String sortedPropName = (String) sortedPropIter.next();
+ String value = props.getProperty(sortedPropName);
+ Status.println(sortedPropName + " = " + value);
+ }
+ }
+}
diff --git a/xploit/xploit.iml b/xploit/xploit.iml
new file mode 100644
index 0000000..ebaa717
--- /dev/null
+++ b/xploit/xploit.iml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file