Skip to content

Commit

Permalink
Merge pull request #61 from /issues/55-malwarelytics-for-android-1.1.1
Browse files Browse the repository at this point in the history
Update Malwarelytics for Android to 1.1.1
  • Loading branch information
TomasKypta authored May 28, 2024
2 parents 4f797a8 + 02c8238 commit ef3fc4b
Show file tree
Hide file tree
Showing 20 changed files with 638 additions and 96 deletions.
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ buildscript {
}

dependencies {
classpath "com.android.tools.build:gradle:7.2.2"
classpath "com.android.tools.build:gradle:8.1.4"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
Expand Down Expand Up @@ -54,8 +54,8 @@ android {
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

}
Expand Down
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Malwarelytics_targetSdkVersion=33
Malwarelytics_compileSdkVersion=33
Malwarelytics_ndkversion=21.4.7075529

Malwarelytics_antivirusVersion=1.0.2
Malwarelytics_antivirusVersion=1.1.1
Malwarelytics_reactNativeVersion=0.71.6

# Required due to processing a massive react-android AAR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.facebook.react.bridge.ReadableMap
import com.wultra.android.antimalware.ApkThreat
import com.wultra.android.antimalware.MalwareFlag
import com.wultra.android.antimalware.smart.SmartProtectionResult
import com.wultra.android.antimalware.update.*
import com.wultra.android.rasp.*

// This file contains functions that converts various objects from native Malwarelytics SDK
Expand Down Expand Up @@ -56,6 +57,25 @@ fun ScreenSharingDetection.toJs(): ReadableMap {
return Arguments.createMap()
.put("isScreenShared", isScreenShared)
.put("numberOfDisplays", numberOfDisplays)
.put("transientData", transientData.toJs())
.put("isProblematic", isProblematic)
.put("isTransientChange", isTransientChange)
}

fun ScreenSharingDetection.TransientScreenSharingData.toJs(): ReadableMap {
// TransientScreenSharingData
return Arguments.createMap()
.put("displayAdded", displayAdded)
.put("displayRemoved", displayRemoved)
}

fun ScreenReaderDetection.toJs(): ReadableMap {
// ScreenReaderInfo
return Arguments.createMap()
.put("isNotAllowedScreenReaderEnabled", isNotAllowedScreenReaderEnabled)
.put("notAllowedScreenReaders", notAllowedScreenReaders)
.put("enabledScreenReaders", enabledScreenReaders)
.put("installedScreenReaders", installedScreenReaders)
}

fun TapjackingDetection.toJs(): ReadableMap {
Expand Down Expand Up @@ -98,6 +118,45 @@ fun MalwareFlag.toJs(): ReadableMap {
.put("type", type.name)
}

fun UpdateInfo.toJs(): ReadableMap {
val sUpdates = successfulUpdates.toList().fold(Arguments.createMap()) { acc, it ->
acc.put(it.first.name, it.second.toJs())
}
val fUpdates = failedUpdates.toList().fold(Arguments.createMap()) { acc, it ->
acc.put(it.first.name, it.second.toJs())
}

// UpdateInfo
return Arguments.createMap()
.put("successfulUpdates", sUpdates)
.put("failedUpdates", fUpdates)
}

fun UpdateInfoSuccess.toJs(): ReadableMap {
// UpdateInfoSuccess
return Arguments.createMap()
.put("lastSuccessTimestamp", lastSuccessTimestamp)
.put("lastSuccessCheckCount", lastSuccessCheckCount)
.put("lastSuccessUpdatedCount", lastSuccessUpdatedCount)
}

fun UpdateInfoFailure.toJs(): ReadableMap {
// UpdateInfoFailure
return Arguments.createMap()
.put("lastFailureTimestamp", lastFailureTimestamp)
.put("lastFailureReason", lastFailureReason)
}

fun ObservedUpdateInfo.toJs(): ReadableMap {
// ObservedUpdateInfo
return Arguments.createMap()
.put("updateResult", updateResult.name)
.put("updateType", updateType.name)
.put("checkedApps", checkedApps)
.put("updatedApps", updatedApps)
.put("failureReason", failureReason)
}

fun ActiveCallDetection.toJs(): ReadableMap {
// ActiveCallInfo
return Arguments.createMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.wultra.android.antimalware.*
import com.wultra.android.antimalware.update.*
import com.wultra.android.appprotection.AppProtection
import com.wultra.android.appprotection.AppProtectionConfig
import com.wultra.android.rasp.*
Expand Down Expand Up @@ -57,7 +59,8 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
enum class Event(val value: String) {
STATE("Malwarelytics.STATE"),
RASP("Malwarelytics.RASP"),
AV("Malwarelytics.AV"),
AV_UPDATE("Malwarelytics.AV_UPDATE"),
AV_APK_THREAT("Malwarelytics.AV_APK_THREAT")
}

/**
Expand Down Expand Up @@ -167,6 +170,10 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
changeState(State.READY)
moduleInitResult = initializationResult
service.getRaspManager().registerRaspObserver(raspObserver)
if (service.getAppProtectionConfig().antivirusConfig.isAntivirusEnabled()) {
service.getAntivirus().getUpdateManager().registerUpdateObserver(updateObserver)
service.getAntivirus().registerApkThreatObserver(apkThreatObserver)
}
// Now protect all activities registered for the tapjacking protection.
helper.protectActivitiesRegisteredForProtection(currentActivity)
// Resolve the promise with initialization result
Expand All @@ -186,6 +193,10 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
changeState(State.PENDING_SHUTDOWN)
}
service.getRaspManager().unregisterRaspObserver(raspObserver)
if (service.getAppProtectionConfig().antivirusConfig.isAntivirusEnabled()) {
service.getAntivirus().getUpdateManager().unregisterUpdateObserver(updateObserver)
service.getAntivirus().unregisterApkThreatObserver(apkThreatObserver)
}
if (clearAvUserId) {
service.releaseAndResetAvUid()
} else {
Expand Down Expand Up @@ -269,6 +280,7 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
RaspMessageType.VPN -> rasp.isVpnEnabled()
RaspMessageType.TAPJACKING -> rasp.getTapjackingDetection().toJs()
RaspMessageType.ADB_STATUS -> rasp.isAdbEnabled()
RaspMessageType.SCREEN_READER -> rasp.getScreenReaderDetection().toJs()
RaspMessageType.NA_SCREEN_READER -> rasp.isNotAllowedScreenReaderEnabled()
RaspMessageType.SCREEN_LOCK -> rasp.isDeviceUsingScreenLock()
RaspMessageType.PLAY_PROTECT -> rasp.isPlayProtectEnabled()
Expand Down Expand Up @@ -302,6 +314,7 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
REPACKAGED,
HTTP_PROXY,
SCREEN_SHARING,
SCREEN_READER,
EMULATOR,
VPN,
TAPJACKING,
Expand Down Expand Up @@ -347,8 +360,12 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
sendRaspEvent(RaspMessageType.SYSTEM_INTEGRITY, rootDetection.toJs(), rootDetection.isRooted)
}

override fun onScreenSharingDetected(screenSharingDetected: Boolean) {
sendRaspEvent(RaspMessageType.SCREEN_SHARING, simpleWritableMap("isScreenShared", screenSharingDetected), screenSharingDetected)
override fun onScreenSharingDetected(screenSharingDetection: ScreenSharingDetection) {
sendRaspEvent(RaspMessageType.SCREEN_SHARING, screenSharingDetection.toJs(), screenSharingDetection.isProblematic)
}

override fun onScreenReaderDetected(screenReaderDetection: ScreenReaderDetection) {
sendRaspEvent(RaspMessageType.SCREEN_READER, screenReaderDetection.toJs(), screenReaderDetection.isNotAllowedScreenReaderEnabled)
}

override fun onTapjackingDetected(tapjackingDetection: TapjackingDetection) {
Expand Down Expand Up @@ -529,6 +546,66 @@ class MalwarelyticsModule(reactContext: ReactApplicationContext) :
}
}

@ReactMethod
fun getLastUpdateInfo(promise: Promise) {
resolveOnQueue(promise) { service ->
service.getAntivirus().getUpdateManager().getLastUpdateInfo().toJs()
}
}

private val updateObserver = object : UpdateObserver {
override fun onSuggestionUpdated(observedUpdateInfo: ObservedUpdateInfo) {
helper.onQueue {
sendEvent(Event.AV_UPDATE, observedUpdateInfo.toJs())
}
}
}

// Observing APK THREAT events

/**
* Messages supported by the RASP part of Malwarelytics.
*/
private enum class ApkThreatMessageType {
INSTALL,
UPDATE,
UNINSTALL
}

private val apkThreatObserver = object : ApkThreatObserver {
override fun onInstallDetected(apkThreat: ApkThreat) {
helper.onQueue {
sendApkThreatEvent(ApkThreatMessageType.INSTALL, apkThreat.toJs())
}
}

override fun onUpdateDetected(apkThreat: ApkThreat) {
helper.onQueue {
sendApkThreatEvent(ApkThreatMessageType.UPDATE, apkThreat.toJs())
}
}

override fun onUninstallDetected(apkThreat: ApkThreat) {
helper.onQueue {
sendApkThreatEvent(ApkThreatMessageType.UNINSTALL, apkThreat.packageName)
}
}
}

/**
* Report APK THREAT event back to the JavaScript.
* @param messageType APK THREAT message type
* @param body Content of message, depending on type.
*/
private fun sendApkThreatEvent(messageType: ApkThreatMessageType, body: Any) {
helper.onQueue {
val payload = Arguments.createMap()
payload.put("type", messageType.name)
payload.putAny("payload", body)
sendEvent(Event.AV_APK_THREAT, payload)
}
}

// Module state and other private functions

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ fun WritableMap.put(key: String, value: Double?): WritableMap {
value?.let { this.putDouble(key, it) }
return this
}
fun WritableMap.put(key: String, value: Long?): WritableMap {
value?.let { this.putDouble(key, it.toDouble()) }
return this
}
fun WritableMap.put(key: String, value: ReadableArray?): WritableMap {
value?.let { this.putArray(key, it) }
return this
Expand All @@ -142,6 +146,7 @@ fun WritableMap.putAny(key: String, value: Any?): WritableMap {
is Float -> putDouble(key, value.toDouble())
is Double -> putDouble(key, value)
is Int -> putInt(key, value)
is Long -> putDouble(key, value.toDouble())
}
return this
}
Expand Down
2 changes: 1 addition & 1 deletion docs/Release-Notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

### Release 1.0.3-dev

- Update Malawrelytics for Android to 1.1.1 (#55, #52)
- Update Malwarelytics for Apple to 2.1.1 (#53)
- Update Malwarelytics for Android to 1.0.2 (#52)
- Update project configuration (#56)
- Fix iOS build (#57)

Expand Down
77 changes: 69 additions & 8 deletions docs/Usage-AV.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The Antivirus module is currently supported only on the Android platform. Therefore, before you start using it, ensure that the functionality is supported on the platform:

```typescript
// The rest of this document will use `antivirus` constant in the examples.
// The rest of this document will use the `antivirus` constant in the examples.
const antivirus = Malwarelytics.sharedInstance.antivirus;
if (!antivirus.isSupported) {
throw new Error("Antivirus is not supported on this platform")
Expand Down Expand Up @@ -45,30 +45,91 @@ threats.forEach((threat) => {
});
```
<!-- begin box info -->
You can get more information from the list, such as detected Malware name. Check `ApkThreat` interface for more details.
You can get more information from the list, such as detected Malware names. Check the `ApkThreat` interface for more details.
<!-- end -->

### Threat levels

The following threat index levels are defined:

- `MALWARE`
- The found threats clearly indicate that the app is a malware.
- The found threats indicate that the app is malware.
- `HIGHLY_DANGEROUS`
- The found threats indicate that the app is highly dangerous to the current app. It uses multiple potential attack vectors including techniques directly targeting the current app.
- `DANGEROUS`
- The found threats indicate that the app is dangerous to the current app. Is uses multiple potential attack vectors. However, no technique directly targeting the current app was detected.
- The found threats indicate that the app is dangerous to the current app. It uses multiple potential attack vectors. However, no technique directly targeting the current app was detected.
- `POTENTIALLY_UNWANTED_APP`
- The found threats indicate that the app might be potentially dangerous. For example it declares potentially dangerous permissions. However it it quite possible that the app is legitimate.
- The found threats indicate that the app might be potentially dangerous. For example, it declares potentially dangerous permissions. However, it is quite possible that the app is legitimate.
- `SAFE`
- There are no found threats.
- `UNKNOWN`
- The threat is unknown. The app was probably not found. In case of suggestions, there's none.
- The threat is unknown. The app was probably not found. In the case of suggestions, there are none.

## Listen to App Changes

The app can listen to changes in installed applications (and changes in app threats) - app installs, updates, and uninstalls. To listen to these events, you have to register a listener that implements the `MalwarelyticsAndroidApkThreatListener`.

```typescript
await Malwarelytics.sharedInstance.antivirus.setApkThreatListener({
onInstallDetected(apkThreat: ApkThreat): void {
console.log(`App install observed: ${JSON.stringify(apkThreat)}`)
}

onUpdateDetected(apkThreat: ApkThreat): void {
console.log(`App update observed: ${JSON.stringify(apkThreat)}`)
}

onUninstallDetected(packageName: string): void {
console.log(`App uninstall observed: ${JSON.stringify(packageName)}`)
}
});
```

To remove the previously set listener, use the following code:
```typescript
Malwarelytics.sharedInstance.antivirus.removeApkThreatListener();
```

## Listen to Suggestion Updates

App evaluation data obtained from the remote server are called suggestions. These data are automatically updated in the background. An app can listen to these updates.

To listen to suggestion updates, you have to register a listener that implements the `MalwarelyticsAndroidUpdateListener` interface.

```typescript
await Malwarelytics.sharedInstance.antivirus.setUpdateListener({
onSuggestionUpdated(info: ObservedUpdateInfo): void {
console.log(`Update info observed: ${JSON.stringify(info)}`)
}
});
```

To remove the previously set listener, use the following code:

```typescript
Malwarelytics.sharedInstance.antivirus.removeUpdateListener();
```

## Getting the Last Update Info

The antivirus API offers a method for obtaining information about the last updates.

Performed updates are of two types:
- `FULL` - Suggestions for all apps were updated.
- `PARTIAL` - Suggestions for only some apps were updated.

For each of these types of updates, the data contains info about the latest successful and failed updates.

The data can be obtained by using the following code:

```typescript
let updateInfo = await Malwarelytics.sharedInstance.antivirus.getLastUpdateInfo();
```


## Trigger Smart Protection Update

To trigger a Smart Protection update and evaluation use the follwing code:
To trigger a Smart Protection update and evaluation use the following code:

```typescript
const onlineUpdate = true;
Expand All @@ -87,7 +148,7 @@ if (result.uiDisplayed) {
The operation above might result in displaying a UI (based on the found threats). Note that the UI will be displayed (if the config allows it) after a small delay. That's because the method performs update and evaluation first.

<!-- begin box info -->
If you want to change visual style of the displayed UI then follow instructions in [Configuration of the Antivirus UI for Android](Configuration-Antivirus-UI.md) document.
If you want to change the visual style of the displayed UI then follow the instructions in the [Configuration of the Antivirus UI for Android](Configuration-Antivirus-UI.md) document.
<!-- end -->

## Change language
Expand Down
Loading

0 comments on commit ef3fc4b

Please sign in to comment.