Skip to content

Commit

Permalink
Merge pull request #212 from simondankelmann/simondankelmann/spamDete…
Browse files Browse the repository at this point in the history
…ction

Simondankelmann/spam detection
  • Loading branch information
simondankelmann committed Jan 5, 2024
2 parents 72e0aee + c70271a commit edcc165
Show file tree
Hide file tree
Showing 41 changed files with 1,951 additions and 39 deletions.
15 changes: 1 addition & 14 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ With some modifications to the advertised BLE package, it is possible to trigger

> **_NOTE:_** To achieve optimal results, it is recommended to set the advertising interval to a value between 20 and 100 milliseconds. Additionally, locking and unlocking the targeted iPhone can aid in the process.
### Spam Detector (Detect Spammers)
The Spam Detector tool allows you to detect nearby spammers, even on the lock screen. It can identify spam from Flipper Zeros, our app, and various other software and scripts. Once your device has detected spam, it will send you a notification, indicating whether it was sent by a Flipper Zero or another source.

### Lovespouse (Adult Toys)
With this functionality, it is possible to enable or disable various adult toys that support the Lovespouse app. Additionally, the "Denial of Pleasure" can be executed by selecting the Lovespouse Stops List and setting the repeat mode to Repeat List. More information on this topic can be found [here](https://mandomat.github.io/2023-11-13-denial-of-pleasure/).

Expand Down Expand Up @@ -64,6 +67,8 @@ If you're an end-user looking for a fully functional app, download the Release A

- [tutozz](https://github.com/tutozz) for the research of Easy Setup Buds in Android

- [K3YOMI](https://github.com/K3YOMI) for the spam detector idea

- And special thanks to anyone else who has been involved in prior research and publications related to this topic.

## Screenshots
Expand Down
Binary file modified app/debug/app-debug.apk
Binary file not shown.
Binary file modified app/release/app-release.apk
Binary file not shown.
17 changes: 12 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
If your app doesn't use Bluetooth scan results to derive physical
location information, you can
<a href="#assert-never-for-location">strongly assert that your app
doesn't derive physical location</a>.
doesn't derive physical location</a>. -->

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

<!--
Needed only if your app makes the device discoverable to Bluetooth
devices.
-->
Expand All @@ -27,10 +28,10 @@
devices.
-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!--Needed only if your app uses Bluetooth scan results to derive physical location. -->
<!--

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-->

<uses-feature
android:name="android.hardware.bluetooth_le"
Expand All @@ -39,6 +40,9 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />




<application
Expand All @@ -51,7 +55,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.BluetoothLESpam.NoActionBar"
tools:targetApi="31">
<service android:foregroundServiceType="specialUse" android:name=".Services.AdvertisementForegroundService"></service>
<service android:foregroundServiceType="specialUse" android:name=".Services.AdvertisementForegroundService" />

<service android:foregroundServiceType="location" android:name=".Services.BluetoothLeScanForegroundService" />

<receiver android:name=".Services.AdvertisementForegroundService$ToggleButtonListener" />
<receiver android:name=".Services.AdvertisementForegroundService$StopButtonListener" />
<activity
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package de.simon.dankelmann.bluetoothlespam.Adapters

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import de.simon.dankelmann.bluetoothlespam.Enums.FlipperDeviceType
import de.simon.dankelmann.bluetoothlespam.Models.FlipperDeviceScanResult
import de.simon.dankelmann.bluetoothlespam.Models.SpamPackageScanResult
import de.simon.dankelmann.bluetoothlespam.R

/*
class FlipperDeviceScanResultListViewAdapter(private val context: Activity, var flipperDevices: MutableList<FlipperDeviceScanResult>) : ArrayAdapter<FlipperDeviceScanResult>(context, R.layout.list_item_flipper_device_scan_result, flipperDevices) {
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
val inflater = context.layoutInflater
val rowView = inflater.inflate(R.layout.list_item_flipper_device_scan_result, null, true)
val nameText = rowView.findViewById(R.id.list_item_flipper_device_scan_result_name) as TextView
val addressText = rowView.findViewById(R.id.list_item_flipper_device_scan_result_address) as TextView
val rssiText = rowView.findViewById(R.id.list_item_flipper_device_scan_result_rssi) as TextView
val deviceTypeText = rowView.findViewById(R.id.list_item_flipper_device_scan_result_deviceType) as TextView
//val icon = rowView.findViewById(R.id.list_item_flipper_device_scan_result_icon) as ImageView
nameText.text = flipperDevices[position].deviceName
addressText.text = flipperDevices[position].address
rssiText.text = flipperDevices[position].rssi.toString() + " dBm"
deviceTypeText.text = when(flipperDevices[position].flipperDeviceType){
FlipperDeviceType.FLIPPER_ZERO_WHITE -> "Flipper Zero White"
FlipperDeviceType.FLIPPER_ZERO_BLACK -> "Flipper Zero Black"
FlipperDeviceType.FLIPPER_ZERO_TRANSPARENT -> "Flipper ZeroTransparent"
FlipperDeviceType.UNKNOWN -> "Unknown Flipper Zero"
}
return rowView
}
}*/

class FlipperDeviceScanResultListViewAdapter(var mList: MutableList<FlipperDeviceScanResult>) : RecyclerView.Adapter<FlipperDeviceScanResultListViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// inflates the card_view_design view
// that is used to hold list item
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_flipper_device_scan_result, parent, false)
return ViewHolder(view)
}

// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {

val ItemsViewModel = mList[position]

holder.nameText.text = ItemsViewModel.deviceName
holder.addressText.text = ItemsViewModel.address
holder.rssiText.text = "${ItemsViewModel.rssi} dBm"

holder.deviceTypeText.text = when(ItemsViewModel.flipperDeviceType){
FlipperDeviceType.FLIPPER_ZERO_WHITE -> "Flipper Zero White"
FlipperDeviceType.FLIPPER_ZERO_BLACK -> "Flipper Zero Black"
FlipperDeviceType.FLIPPER_ZERO_TRANSPARENT -> "Flipper ZeroTransparent"
FlipperDeviceType.UNKNOWN -> "Unknown Flipper Zero"
}
}

// return the number of the items in the list
override fun getItemCount(): Int {
return mList.size
}

// Holds the views for adding it to image and text
class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
val nameText = ItemView.findViewById(R.id.list_item_flipper_device_scan_result_name) as TextView
val addressText = ItemView.findViewById(R.id.list_item_flipper_device_scan_result_address) as TextView
val rssiText = ItemView.findViewById(R.id.list_item_flipper_device_scan_result_rssi) as TextView
val deviceTypeText = ItemView.findViewById(R.id.list_item_flipper_device_scan_result_deviceType) as TextView
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package de.simon.dankelmann.bluetoothlespam.Adapters

import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import de.simon.dankelmann.bluetoothlespam.AppContext.AppContext
import de.simon.dankelmann.bluetoothlespam.Enums.FlipperDeviceType
import de.simon.dankelmann.bluetoothlespam.Enums.SpamPackageType
import de.simon.dankelmann.bluetoothlespam.Models.FlipperDeviceScanResult
import de.simon.dankelmann.bluetoothlespam.Models.SpamPackageScanResult
import de.simon.dankelmann.bluetoothlespam.R


/*
class SpamPackageScanResultListViewAdapter(private val context: Activity, var spamPackages: MutableList<SpamPackageScanResult>) : ArrayAdapter<SpamPackageScanResult>(context, R.layout.list_item_spam_package_scan_result, spamPackages) {
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
val inflater = context.layoutInflater
val rowView = inflater.inflate(R.layout.list_item_spam_package_scan_result, null, true)
val nameText = rowView.findViewById(R.id.list_item_spam_package_scan_result_name) as TextView
val addressText = rowView.findViewById(R.id.list_item_spam_package_scan_result_address) as TextView
val rssiText = rowView.findViewById(R.id.list_item_spam_package_scan_result_rssi) as TextView
val deviceTypeText = rowView.findViewById(R.id.list_item_spam_package_scan_result_deviceType) as TextView
nameText.text = spamPackages[position].spamPackageType.toString()
addressText.text = spamPackages[position].address
rssiText.text = spamPackages[position].rssi.toString() + " dBm"
deviceTypeText.text = spamPackages[position].deviceName
return rowView
}
}
*/

class SpamPackageScanResultListViewAdapter(var mList: MutableList<SpamPackageScanResult>) : RecyclerView.Adapter<SpamPackageScanResultListViewAdapter.ViewHolder>() {
// create new views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// inflates the card_view_design view
// that is used to hold list item
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_spam_package_scan_result, parent, false)
return ViewHolder(view)
}

// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {

val ItemsViewModel = mList[position]

holder.nameText.text = when(ItemsViewModel.spamPackageType){
SpamPackageType.UNKNOWN -> "Unknown Spam"
SpamPackageType.FAST_PAIRING -> "Fast Pairing"
SpamPackageType.CONTINUITY_NEW_AIRTAG -> "Continuity Airtag"
SpamPackageType.CONTINUITY_NEW_DEVICE -> "Continuity new Device"
SpamPackageType.CONTINUITY_NOT_YOUR_DEVICE -> "Continuity not your Device"
SpamPackageType.CONTINUITY_ACTION_MODAL -> "Continuity Action Modal"
SpamPackageType.CONTINUITY_IOS_17_CRASH -> "Continuity iOS 17 Crash"
SpamPackageType.SWIFT_PAIRING -> "Swift Pairing"
SpamPackageType.EASY_SETUP_WATCH -> "Easy Setup Watch"
SpamPackageType.EASY_SETUP_BUDS -> "Easy Setup Buds"
SpamPackageType.LOVESPOUSE_PLAY -> "Lovespouse Play"
SpamPackageType.LOVESPOUSE_STOP -> "Lovespouse Stop"
}

var spamPackageIcon:Drawable = when(ItemsViewModel.spamPackageType){
SpamPackageType.UNKNOWN -> AppContext.getActivity().resources.getDrawable(R.drawable.bluetooth, AppContext.getContext().theme)
SpamPackageType.FAST_PAIRING -> AppContext.getActivity().resources.getDrawable(R.drawable.ic_android, AppContext.getContext().theme)
SpamPackageType.CONTINUITY_NEW_AIRTAG -> AppContext.getActivity().resources.getDrawable(R.drawable.apple, AppContext.getContext().theme)
SpamPackageType.CONTINUITY_NEW_DEVICE -> AppContext.getActivity().resources.getDrawable(R.drawable.apple, AppContext.getContext().theme)
SpamPackageType.CONTINUITY_NOT_YOUR_DEVICE -> AppContext.getActivity().resources.getDrawable(R.drawable.apple, AppContext.getContext().theme)
SpamPackageType.CONTINUITY_ACTION_MODAL -> AppContext.getActivity().resources.getDrawable(R.drawable.apple, AppContext.getContext().theme)
SpamPackageType.CONTINUITY_IOS_17_CRASH -> AppContext.getActivity().resources.getDrawable(R.drawable.apple, AppContext.getContext().theme)
SpamPackageType.SWIFT_PAIRING -> AppContext.getActivity().resources.getDrawable(R.drawable.microsoft, AppContext.getContext().theme)
SpamPackageType.EASY_SETUP_WATCH -> AppContext.getActivity().resources.getDrawable(R.drawable.samsung, AppContext.getContext().theme)
SpamPackageType.EASY_SETUP_BUDS -> AppContext.getActivity().resources.getDrawable(R.drawable.samsung, AppContext.getContext().theme)
SpamPackageType.LOVESPOUSE_PLAY -> AppContext.getActivity().resources.getDrawable(R.drawable.heart, AppContext.getContext().theme)
SpamPackageType.LOVESPOUSE_STOP -> AppContext.getActivity().resources.getDrawable(R.drawable.heart, AppContext.getContext().theme)
}

holder.icon.setImageDrawable(spamPackageIcon)


holder.addressText.text = ItemsViewModel.address
holder.rssiText.text = "${ItemsViewModel.rssi} dBm"
holder.deviceTypeText.text = ""
}

// return the number of the items in the list
override fun getItemCount(): Int {
return mList.size
}

// Holds the views for adding it to image and text
class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
val nameText = ItemView.findViewById(R.id.list_item_spam_package_scan_result_name) as TextView
val addressText = ItemView.findViewById(R.id.list_item_spam_package_scan_result_address) as TextView
val rssiText = ItemView.findViewById(R.id.list_item_spam_package_scan_result_rssi) as TextView
val deviceTypeText = ItemView.findViewById(R.id.list_item_spam_package_scan_result_deviceType) as TextView
val icon = ItemView.findViewById(R.id.list_item_spam_package_scan_result_icon) as ImageView

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.bluetooth.le.AdvertisingSetParameters
import android.util.Log
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingSetCallback
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingCallback
import de.simon.dankelmann.bluetoothlespam.Constants.Constants
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertiseMode
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetRange
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetType
Expand All @@ -18,7 +19,7 @@ import de.simon.dankelmann.bluetoothlespam.Models.AdvertisementSet
import de.simon.dankelmann.bluetoothlespam.Models.ManufacturerSpecificData
import kotlin.random.Random

class ContinuityActionModalAdvertisementSetGenerator: IAdvertisementSetGenerator {
class ContinuityActionModalAdvertisementSetGenerator: IAdvertisementSetGenerator {

private val _logTag = "ContinuityActionModalAdvertisementSetGenerator"

Expand Down Expand Up @@ -83,7 +84,7 @@ class ContinuityActionModalAdvertisementSetGenerator: IAdvertisementSetGenerator
}
}

private val _manufacturerId = 76 // 0x004c == 76 = Apple
private val _manufacturerId = Constants.MANUFACTURER_ID_APPLE
override fun getAdvertisementSets(inputData: Map<String, String>?): List<AdvertisementSet> {
var advertisementSets: MutableList<AdvertisementSet> = mutableListOf()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.bluetooth.le.AdvertisingSetParameters
import android.util.Log
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingCallback
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingSetCallback
import de.simon.dankelmann.bluetoothlespam.Constants.Constants
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertiseMode
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetRange
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetType
Expand All @@ -25,7 +26,7 @@ class EasySetupBudsAdvertisementSetGenerator:IAdvertisementSetGenerator{
// Logic also from here:
// https://github.com/tutozz/ble-spam-android/blob/main/app/src/main/java/com/tutozz/blespam/EasySetupSpam.java

private val _manufacturerId = 117 // 0x75 == 117 = Samsung
private val _manufacturerId = Constants.MANUFACTURER_ID_SAMSUNG
private val prependedBudsBytes = StringHelpers.decodeHex("42098102141503210109")
private val appendedBudsBytes = StringHelpers.decodeHex("063C948E00000000C700") // +16FF75

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.bluetooth.le.AdvertisingSetParameters
import android.util.Log
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingCallback
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingSetCallback
import de.simon.dankelmann.bluetoothlespam.Constants.Constants
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertiseMode
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetRange
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetType
Expand All @@ -25,7 +26,7 @@ class LovespousePlayAdvertisementSetGenerator:IAdvertisementSetGenerator {

private val _logTag = "LovespousePlayAdvertisementSetGenerator"

private val lovespousePlays = mapOf(
val lovespousePlays = mapOf(
"E49C6C" to "Classic 1",
"E7075E" to "Classic 2",
"E68E4F" to "Classic 3",
Expand Down Expand Up @@ -55,7 +56,7 @@ class LovespousePlayAdvertisementSetGenerator:IAdvertisementSetGenerator {
"ACD0A2" to "Independent 2-9",
)

private val _manufacturerId = 255 // 0xFF == 255 == Typo Products, LLC
private val _manufacturerId = Constants.MANUFACTURER_ID_TYPO_PRODUCTS

override fun getAdvertisementSets(inputData: Map<String, String>?): List<AdvertisementSet> {
var advertisementSets: MutableList<AdvertisementSet> = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.bluetooth.le.AdvertisingSetParameters
import android.util.Log
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingCallback
import de.simon.dankelmann.bluetoothlespam.Callbacks.GenericAdvertisingSetCallback
import de.simon.dankelmann.bluetoothlespam.Constants.Constants
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertiseMode
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetRange
import de.simon.dankelmann.bluetoothlespam.Enums.AdvertisementSetType
Expand All @@ -25,13 +26,13 @@ class LovespouseStopAdvertisementSetGenerator:IAdvertisementSetGenerator {

private val _logTag = "LovespouseStopAdvertisementSetGenerator"

private val lovespouseStops = mapOf(
val lovespouseStops = mapOf(
"E5157D" to "Classic Stop",
"D5964C" to "Independent 1 Stop",
"A5113F" to "Independent 2 Stop",
)

private val _manufacturerId = 255 // 0xFF == 255 == Typo Products, LLC
private val _manufacturerId = Constants.MANUFACTURER_ID_TYPO_PRODUCTS

override fun getAdvertisementSets(inputData: Map<String, String>?): List<AdvertisementSet> {
var advertisementSets: MutableList<AdvertisementSet> = mutableListOf()
Expand Down
Loading

0 comments on commit edcc165

Please sign in to comment.