From a7aed156c452685e756fa5372249a0a0e0b886d2 Mon Sep 17 00:00:00 2001
From: hammer-83 <48300721+hammer-83@users.noreply.github.com>
Date: Sat, 5 Oct 2024 11:12:53 -0400
Subject: [PATCH] Framework for persisting kernel read/write primitives across
JAR executions (requires new JAR loader): - Exploit independent base
mechanism for kernel memory access persistance - Implement kernel r/w using
ipv6 sockets - Refactored pointer classes to add some protection against
kernel-space page faults - Added firmware-specific key offsets class. For
now, only 1.02 is supported
---
pom.xml | 2 +-
.../org/ps5jb/sdk/core/AbstractPointer.java | 31 +++-
.../main/java/org/ps5jb/sdk/core/Pointer.java | 5 +-
...dkSoftwareVersionUnsupportedException.java | 44 +++++
.../sdk/core/kernel/KernelAccessorIPv6.java | 47 ++++--
.../ps5jb/sdk/core/kernel/KernelOffsets.java | 50 ++++++
.../ps5jb/sdk/core/kernel/KernelPointer.java | 44 +++--
.../ps5jb/sdk/core/kernel/package-info.java | 2 +-
.../java/org/ps5jb/sdk/lib/LibKernel.java | 15 ++
.../java/org/ps5jb/loader/KernelAccessor.java | 32 ++++
.../org/ps5jb/loader/KernelReadWrite.java | 154 ++++++++++++++++++
.../java/org/ps5jb/loader/RemoteLogger.java | 1 +
.../main/java/org/ps5jb/client/JarMain.java | 15 +-
13 files changed, 410 insertions(+), 32 deletions(-)
create mode 100644 sdk/src/main/java/org/ps5jb/sdk/core/SdkSoftwareVersionUnsupportedException.java
create mode 100644 sdk/src/main/java/org/ps5jb/sdk/core/kernel/KernelOffsets.java
create mode 100644 xlet/src/main/java/org/ps5jb/loader/KernelAccessor.java
create mode 100644 xlet/src/main/java/org/ps5jb/loader/KernelReadWrite.java
diff --git a/pom.xml b/pom.xml
index 6de9a69..957a4e2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
sceKernelGetProsperoSystemSwVersion
+ * method in libkernel
. Last two bytes of the result return
+ * the minor and the major version of the firmware.
+ *
+ * @param softwareVersion Firmware version in the form 0x[MAJOR BYTE][MINOR BYTE]
+ */
+ public KernelOffsets(int softwareVersion) {
+ switch (softwareVersion) {
+ case 0x0102:
+ {
+ OFFSET_KERNEL_DATA = 0x01B40000;
+
+ OFFSET_KERNEL_DATA_BASE_ALLPROC = 0x026D1BF8;
+ OFFSET_KERNEL_DATA_BASE_SECURITYFLAGS = 0x06241074;
+ OFFSET_KERNEL_DATA_BASE_ROOTVNODE = 0x06565540;
+ break;
+ }
+ default:
+ String strSwVersion = MessageFormat.format(
+ "{0,number,#0}.{1,number,00}",
+ new Object[] {
+ new Integer((softwareVersion >> 8) & 0xFF),
+ new Integer(softwareVersion & 0xFF)
+ }
+ );
+ throw new SdkSoftwareVersionUnsupportedException(strSwVersion);
+ }
+ }
+}
diff --git a/sdk/src/main/java/org/ps5jb/sdk/core/kernel/KernelPointer.java b/sdk/src/main/java/org/ps5jb/sdk/core/kernel/KernelPointer.java
index 81763a0..535c321 100644
--- a/sdk/src/main/java/org/ps5jb/sdk/core/kernel/KernelPointer.java
+++ b/sdk/src/main/java/org/ps5jb/sdk/core/kernel/KernelPointer.java
@@ -1,5 +1,7 @@
package org.ps5jb.sdk.core.kernel;
+import org.ps5jb.loader.KernelAccessor;
+import org.ps5jb.loader.KernelReadWrite;
import org.ps5jb.sdk.core.AbstractPointer;
/**
@@ -8,6 +10,11 @@
* by calling {@link KernelReadWrite#setAccessor(KernelAccessor)}.
*/
public class KernelPointer extends AbstractPointer {
+ private static final long serialVersionUID = 3445279334363239500L;
+
+ /** Start of kernel address space. See machine/vmparam.h */
+ private static final long KERNEL_ADDR_MASK = 0xFFFF800000000000L;
+
/**
* Returns a pointer instance equivalent to the given native memory address.
*
@@ -18,6 +25,20 @@ public static KernelPointer valueOf(long addr) {
return addr == 0 ? NULL : new KernelPointer(addr);
}
+ /**
+ * Validates that the given kernel pointer has the correct kernel-space range.
+ *
+ * @param pointer Pointer to validate.
+ * @return Same pointer instance.
+ * @throws IllegalAccessError If the pointer is invalid (including NULL).
+ */
+ public static KernelPointer validRange(KernelPointer pointer) {
+ if ((((pointer.addr() & KERNEL_ADDR_MASK) != KERNEL_ADDR_MASK) || pointer.addr() == -1)) {
+ throw new IllegalAccessError(pointer.toString());
+ }
+ return pointer;
+ }
+
/** Static constant for NULL pointer. */
public static final KernelPointer NULL = new KernelPointer(0);
@@ -31,31 +52,31 @@ public KernelPointer(long addr, Long size) {
@Override
public byte read1(long offset) {
- overflow(this, offset, 1);
+ overflow(validRange(this), offset, 1);
return KernelReadWrite.getAccessor().read1(this.addr + offset);
}
@Override
public short read2(long offset) {
- overflow(this, offset, 2);
+ overflow(validRange(this), offset, 2);
return KernelReadWrite.getAccessor().read2(this.addr + offset);
}
@Override
public int read4(long offset) {
- overflow(this, offset, 4);
+ overflow(validRange(this), offset, 4);
return KernelReadWrite.getAccessor().read4(this.addr + offset);
}
@Override
public long read8(long offset) {
- overflow(this, offset, 8);
+ overflow(validRange(this), offset, 8);
return KernelReadWrite.getAccessor().read8(this.addr + offset);
}
@Override
public void read(long offset, byte[] value, int valueOffset, int size) {
- overflow(this, offset, size);
+ overflow(validRange(this), offset, size);
// TODO: This can be implemented more efficiently
final KernelAccessor accessor = KernelReadWrite.getAccessor();
@@ -66,31 +87,31 @@ public void read(long offset, byte[] value, int valueOffset, int size) {
@Override
public void write1(long offset, byte value) {
- overflow(this, offset, 1);
+ overflow(validRange(this), offset, 1);
KernelReadWrite.getAccessor().write1(this.addr + offset, value);
}
@Override
public void write2(long offset, short value) {
- overflow(this, offset, 2);
+ overflow(validRange(this), offset, 2);
KernelReadWrite.getAccessor().write2(this.addr + offset, value);
}
@Override
public void write4(long offset, int value) {
- overflow(this, offset, 4);
+ overflow(validRange(this), offset, 4);
KernelReadWrite.getAccessor().write4(this.addr + offset, value);
}
@Override
public void write8(long offset, long value) {
- overflow(this, offset, 8);
+ overflow(validRange(this), offset, 8);
KernelReadWrite.getAccessor().write8(this.addr + offset, value);
}
@Override
public void write(long offset, byte[] value, int valueOffset, int count) {
- overflow(this, offset, count);
+ overflow(validRange(this), offset, count);
// TODO: This can be implemented more efficiently
final KernelAccessor accessor = KernelReadWrite.getAccessor();
@@ -108,9 +129,6 @@ public void write(long offset, byte[] value, int valueOffset, int count) {
* @throws IndexOutOfBoundsException If the read or the write beyond one of the two pointers' sizes occurs.
*/
public void copyTo(KernelPointer dest, long offset, int size) {
- overflow(this, offset, size);
- overflow(dest, 0, size);
-
byte[] data = new byte[size];
read(offset, data, 0, size);
dest.write(0, data, 0, size);
diff --git a/sdk/src/main/java/org/ps5jb/sdk/core/kernel/package-info.java b/sdk/src/main/java/org/ps5jb/sdk/core/kernel/package-info.java
index 1f220c7..98cf033 100644
--- a/sdk/src/main/java/org/ps5jb/sdk/core/kernel/package-info.java
+++ b/sdk/src/main/java/org/ps5jb/sdk/core/kernel/package-info.java
@@ -2,6 +2,6 @@
* This package contains various classes related to Kernel access.
* Normally, any of the classes in this package are only usable if
* a global kernel accessor has been installed using
- * {@link org.ps5jb.sdk.core.kernel.KernelReadWrite#setAccessor(org.ps5jb.sdk.core.kernel.KernelAccessor)}
+ * {@link org.ps5jb.loader.KernelReadWrite#setAccessor(org.ps5jb.loader.KernelAccessor)}
*/
package org.ps5jb.sdk.core.kernel;
diff --git a/sdk/src/main/java/org/ps5jb/sdk/lib/LibKernel.java b/sdk/src/main/java/org/ps5jb/sdk/lib/LibKernel.java
index 9044087..578ebc1 100644
--- a/sdk/src/main/java/org/ps5jb/sdk/lib/LibKernel.java
+++ b/sdk/src/main/java/org/ps5jb/sdk/lib/LibKernel.java
@@ -446,6 +446,10 @@ public int mprotect(Pointer addr, long len, int prot) {
return (int) call(mprotect, addr.addr(), len, prot);
}
+ public int sched_yield() {
+ return sched_yield(0);
+ }
+
public int sched_yield(long unused) {
if (sched_yield == null) {
sched_yield = addrOf("sched_yield");
@@ -492,4 +496,15 @@ public byte[] sceKernelGetProsperoSystemSwVersion() {
buf.free();
}
}
+
+ /**
+ * Helper method to return the system software version in the form 0x[MAJOR BYTE][MINOR BYTE]
.
+ *
+ * @return Firmware version of the console.
+ * @throws org.ps5jb.sdk.core.SdkRuntimeException If an error occurred while retrieving the version.
+ */
+ public int getSystemSoftwareVersion() {
+ byte[] swVer = sceKernelGetProsperoSystemSwVersion();
+ return (swVer[0x1F] << 8) | swVer[0x1E];
+ }
}
diff --git a/xlet/src/main/java/org/ps5jb/loader/KernelAccessor.java b/xlet/src/main/java/org/ps5jb/loader/KernelAccessor.java
new file mode 100644
index 0000000..3503024
--- /dev/null
+++ b/xlet/src/main/java/org/ps5jb/loader/KernelAccessor.java
@@ -0,0 +1,32 @@
+package org.ps5jb.loader;
+
+import java.io.Serializable;
+
+/**
+ *
+ * Interface for accessing Kernel memory. Various exploits able to + * do so should implement this interface. + *
+ *+ * The kernel accessor implementations should be Serializable. This is because + * Jar Loader has a different classloader than a JAR that will activate the kernel access. + * So when a JAR ends execution, Kernel Accessor state will be serialized rather than being + * stored as class instance. Upon execution of another JAR, the kernel accessor will + * be deserialized and activated again. Assuming that all JARs contain the same kernel + * accessor implementation, the kernel accessor will remain in place for subsequent JAR + * executions after it is activated initially. + *
+ */ +public interface KernelAccessor extends Serializable { + byte read1(long kernelAddress); + short read2(long kernelAddress); + int read4(long kernelAddress); + long read8(long kernelAddress); + + void write1(long kernelAddress, byte value); + void write2(long kernelAddress, short value); + void write4(long kernelAddress, int value); + void write8(long kernelAddress, long value); + + long getKernelBase(); +} diff --git a/xlet/src/main/java/org/ps5jb/loader/KernelReadWrite.java b/xlet/src/main/java/org/ps5jb/loader/KernelReadWrite.java new file mode 100644 index 0000000..f6bc153 --- /dev/null +++ b/xlet/src/main/java/org/ps5jb/loader/KernelReadWrite.java @@ -0,0 +1,154 @@ +package org.ps5jb.loader; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; + +/** + * Class managing capability of SDK to read/write the kernel memory. + */ +public final class KernelReadWrite { + private static KernelAccessor kernelAccessor; + + /** Kernel accessor state, serialized by calling {@link #saveAccessor()} */ + private static byte[] kernelAccessorState; + + /** Custom object input stream which can use a specific class loader to find the class */ + private static class ClassLoaderObjectInputStream extends ObjectInputStream { + ClassLoader classLoader; + + /** + * Constructor of the class + * + * @param in Stream from which to read the objects. + * @param classLoader Classloader to use for resolving classes. + * @throws IOException An exception occurred in the underlying stream. + */ + private ClassLoaderObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException { + super(in); + this.classLoader = classLoader; + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + Class result = null; + + if (classLoader != null) { + String className = desc.getName(); + try { + result = Class.forName(className, false, classLoader); + } catch (ClassNotFoundException ex) { + Status.printStackTrace("Could not resolve the class " + className + " using classloader " + classLoader, ex); + } + } + + if (result == null) { + result = super.resolveClass(desc); + } + + return result; + } + } + + /** + * Default constructor + */ + private KernelReadWrite() { + } + + /** + * Register a global instance of a kernel accessor, responsible for + * reading and writing kernel memory. + * + * @param kernelAccessor New accessor instance. + */ + public static synchronized void setAccessor(KernelAccessor kernelAccessor) { + KernelReadWrite.kernelAccessor = kernelAccessor; + } + + /** + * Retrieve a global instance of a kernel accessor. May be null + * if none are installed. + * + * @return Instance of a kernel accessor or null. + */ + public static synchronized KernelAccessor getAccessor() { + return KernelReadWrite.kernelAccessor; + } + + /** + * Saves the current state of a global kernel accessor instance internally and + * sets it tonull
.
+ *
+ * @return True if state was successfully saved.
+ */
+ public static synchronized boolean saveAccessor() {
+ if (kernelAccessor != null) {
+ try {
+ ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream outStr = new ObjectOutputStream(outBytes);
+ try {
+ outStr.writeObject(kernelAccessor);
+ outStr.flush();
+ kernelAccessorState = outBytes.toByteArray();
+ } finally {
+ outStr.close();
+ }
+ } finally {
+ outBytes.close();
+ }
+ } catch (IOException | RuntimeException | Error e) {
+ Status.printStackTrace("Exception occurred while saving the kernel accessor state", e);
+ }
+ } else {
+ kernelAccessorState = null;
+ }
+
+ // If error occurred on close, state is correctly serialized so consider it a success.
+ boolean result = kernelAccessorState != null || kernelAccessor == null;
+ if (kernelAccessorState != null) {
+ kernelAccessor = null;
+ }
+
+ return result;
+ }
+
+ /**
+ * Restores kernel accessor instance from a previously saved state.
+ *
+ * @param classLoader ClassLoader to use to find the kernel accessor class
+ * @return True if kernel accessor was activated. False if there was no previously saved
+ * accessor or if it could not be activated.
+ */
+ public static synchronized boolean restoreAccessor(ClassLoader classLoader) {
+ if (kernelAccessorState != null) {
+ try {
+ ByteArrayInputStream inBytes = new ByteArrayInputStream(kernelAccessorState);
+ try {
+ ClassLoaderObjectInputStream inStr = new ClassLoaderObjectInputStream(inBytes, classLoader);
+ try {
+ kernelAccessor = (KernelAccessor) inStr.readObject();
+ } finally {
+ inStr.close();
+ }
+ } finally {
+ inBytes.close();
+ }
+ } catch (ClassNotFoundException | IOException | RuntimeException | Error e) {
+ Status.printStackTrace("Exception occurred while restoring the kernel accessor", e);
+ }
+ }
+
+ boolean result = kernelAccessor != null;
+ if (result) {
+ kernelAccessorState = null;
+ }
+
+ return result;
+ }
+}
diff --git a/xlet/src/main/java/org/ps5jb/loader/RemoteLogger.java b/xlet/src/main/java/org/ps5jb/loader/RemoteLogger.java
index 98217e6..6c60b58 100644
--- a/xlet/src/main/java/org/ps5jb/loader/RemoteLogger.java
+++ b/xlet/src/main/java/org/ps5jb/loader/RemoteLogger.java
@@ -113,6 +113,7 @@ public void error(String msg, Throwable e) {
} finally {
pw.close();
}
+ sb.append("\n");
sb.append(sw);
} finally {
try {
diff --git a/xploit/src/main/java/org/ps5jb/client/JarMain.java b/xploit/src/main/java/org/ps5jb/client/JarMain.java
index c387b0b..14ac290 100644
--- a/xploit/src/main/java/org/ps5jb/client/JarMain.java
+++ b/xploit/src/main/java/org/ps5jb/client/JarMain.java
@@ -8,6 +8,8 @@
import java.util.Enumeration;
import java.util.jar.Manifest;
+import org.ps5jb.loader.KernelAccessor;
+import org.ps5jb.loader.KernelReadWrite;
import org.ps5jb.loader.Status;
/**
@@ -86,14 +88,25 @@ protected void execute() throws Exception {
try {
Class payloadClass = Class.forName(payloadName);
-
Status.println("Executing payload: " + payloadName);
+
+ // Activate Kernel accessor, if any
+ if (!KernelReadWrite.restoreAccessor(getClass().getClassLoader())) {
+ Status.println("Kernel R/W will not be available");
+ } else {
+ Status.println("Kernel R/W restored");
+ }
+
Runnable payload = (Runnable) payloadClass.newInstance();
payload.run();
} catch (ClassNotFoundException e) {
Status.println("Unable to determine the payload to execute because the value of the attribute '" + MANIFEST_PAYLOAD_KEY + "' is not recognized: " + payloadName);
} catch (ClassCastException e) {
Status.println("Unable to execute the payload because it does not implement the " + Runnable.class.getName() + " interface");
+ } finally {
+ if (KernelReadWrite.getAccessor() != null && KernelReadWrite.saveAccessor()) {
+ Status.println("Kernel R/W serialized for a follow-up execution");
+ }
}
}
}