react-native-malwarelytics
Version:
Malwarelytics for React Native protects your banking or fintech app from a broad range of mobile security threats with an industry-leading mobile threat intelligence solution.
503 lines (448 loc) • 16.2 kB
text/typescript
//
// Copyright 2023 Wultra s.r.o.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions
// and limitations under the License.
//
import type { EmitterSubscription } from "react-native";
import type { EmulatorInfo } from "./model/rasp/EmulatorInfo";
import type { HttpProxyInfo } from "./model/rasp/HttpProxyInfo";
import type { RepackagingInfo } from "./model/rasp/RepackageInfo";
import type { ScreenSharingInfo } from "./model/rasp/ScreenSharingInfo";
import type { ScreenReaderInfo } from "./model/rasp/ScreenReaderInfo";
import type { SystemIntegrityInfo } from "./model/rasp/SystemIntegrityInfo";
import type { TapjackingInfo } from "./model/rasp/TapjackingInfo";
import type { ActiveCallInfo } from "./model/rasp/ActiveCallInfo";
import type { AppPresenceInfo } from "./model/rasp/AppPresenceInfo";
import type { DebuggerInfo } from "./model/rasp/DebuggerInfo";
import type { BiometryInfo } from "./model/rasp/BiometryInfo";
import type { EventHelper } from "./internal/EventHelper";
import type { RaspEvent, RaspEventType } from "./internal/RaspEvent";
import { MalwarelyticsModuleIfc, wrapNativeCall } from "./internal/MalwarelyticsModule";
import { Platform } from "react-native";
import { MalwarelyticsError } from "./MalwarelyticsError";
/**
* Malwarelytics RASP module.
*/
export class MalwarelyticsRasp {
constructor(eventHelper: EventHelper) {
this.eventHelper = eventHelper
this.module = eventHelper.module
}
/**
* Instance of EventHelper shared with Malwarelytics class.
*/
private readonly eventHelper: EventHelper
/**
* Instance of native module interface.
*/
private readonly module: MalwarelyticsModuleIfc
/**
* Object representing a subscription to RASP events.
*/
private raspEventsSubscription: EmitterSubscription | undefined
/**
* Set listener for RASP events.
* @param listener Listener implementation.
*/
async setRaspListener(listener: MalwarelyticsRaspListener): Promise<void> {
this.raspEventsSubscription?.remove()
this.raspEventsSubscription = await this.eventHelper.addListener('Malwarelytics.RASP', (data) => {
//console.log(`${Platform.OS}: RASP event: ${JSON.stringify(data)}`)
const m = data as RaspEvent
switch (m.type) {
// Apple + Android
case "DEBUGGER":
listener.debuggerDetected(m.payload as boolean);
break
case "REPACKAGED":
listener.repackagingDetected(m.payload as RepackagingInfo)
break
case "SYSTEM_INTEGRITY":
listener.systemIntegrityCompromised(m.payload as SystemIntegrityInfo)
break
case "HTTP_PROXY":
listener.httpProxyDetected(m.payload as HttpProxyInfo)
break
case "SCREEN_SHARING":
listener.screenSharingDetected(m.payload as ScreenSharingInfo)
break
case "EMULATOR":
listener.emulatorDetected(m.payload as EmulatorInfo)
break
case "VPN":
listener.vpnDetected(m.payload as boolean)
break
case "APP_PRESENCE":
listener.appPresenceChangeDetected(m.payload as AppPresenceInfo)
break;
// Android specific
case "SCREEN_READER":
listener.screenReaderDetected(m.payload as ScreenReaderInfo)
break
case "TAPJACKING":
listener.tapjackingDetected(m.payload as TapjackingInfo)
break
case "ADB_STATUS":
listener.adbStatusDetected(m.payload as boolean)
break
case "ACTIVE_CALL":
listener.activeCallDetected(m.payload as ActiveCallInfo)
break;
// Apple specific
case "SCREENSHOT":
listener.userScreenshotDetected()
break
case "REVERSE_TOOLS":
listener.reverseEngineeringToolsDetected()
break
case "DEVICE_PASSCODE":
listener.systemPasscodeConfigurationChanged(m.payload as boolean)
break
case "DEVICE_BIOMETRY":
listener.systemBiometryConfigurationChanged(m.payload as boolean)
break
case "ON_CALL":
listener.isOnCallChanged(m.payload as boolean)
break
default:
console.warn(`${Platform.OS}: Unsupported RASP event ${m.type}`)
break
}
})
}
/**
* Remove RASP listener previously set by `setRaspListener()` method.
*/
removeRaspListener() {
this.raspEventsSubscription?.remove()
this.raspEventsSubscription = undefined
}
// Android + Apple
/**
* Get information about Jailbreak or Root presence on the device.
*/
getSystemIntegrityInfo(): Promise<SystemIntegrityInfo> {
return this.getRaspInfo("SYSTEM_INTEGRITY")
}
/**
* Get information whether app is running in emulator. You can use `getEmulatorInfo()` method to get more details
* about the emulator type.
*/
async isRunningInEmulator(): Promise<boolean> {
return (await this.getEmulatorInfo()).isEmulator
}
/**
* Get information whether debugger is connected.
*/
isDebuggerConnected(): Promise<boolean> {
return this.getRaspInfo("DEBUGGER")
}
/**
* Get detailed information about debugger detection.
*/
getDebuggerInfo(): Promise<DebuggerInfo> {
return this.getRaspAndroidInfo("DEBUGGER_INFO")
}
/**
* Get information about application repackaging.
*/
getRepackagingInfo(): Promise<RepackagingInfo> {
return this.getRaspInfo("REPACKAGED")
}
/**
* Get information about HTTP proxy configured on the system.
*/
getHttpProxyInfo(): Promise<HttpProxyInfo> {
return this.getRaspInfo("HTTP_PROXY")
}
/**
* Get information whether app is running in emulator.
*/
getEmulatorInfo(): Promise<EmulatorInfo> {
return this.getRaspInfo("EMULATOR")
}
/**
* Get information about active screen sharing or screen capturing.
*/
getScreenSharingInfo(): Promise<ScreenSharingInfo> {
return this.getRaspInfo("SCREEN_SHARING")
}
/**
* Get information about active VPN connection.
*/
isVpnActive(): Promise<boolean> {
return this.getRaspInfo("VPN")
}
/**
* Get information about the active phone call.
*/
isOnCall(): Promise<boolean> {
return this.getRaspInfo("ON_CALL")
}
/**
* Obtain information about app presence.
*/
getAppPresenceInfo(): Promise<AppPresenceInfo> {
return this.getRaspInfo("APP_PRESENCE")
}
// Apple specific
/**
* Apple specific: Get information whether reverse engineering tools are present on the device.
*/
isReverseEngineeringToolsPresent(): Promise<boolean> {
return this.getRaspAppleInfo("REVERSE_TOOLS")
}
/**
* Apple specific: Get information about enabled passcode in the system (device lock)
*/
isSystemPasscodeEnabled(): Promise<boolean> {
return this.getRaspAppleInfo("DEVICE_PASSCODE")
}
/**
* Apple specific: Get information about biometry enrolled by the user in the system.
*/
isSystemBiometryEnabled(): Promise<boolean> {
return this.getRaspAppleInfo("DEVICE_BIOMETRY")
}
// Android specific
/**
* Android specific: Get information about tapjacking.
*/
getTapjackingInfo(): Promise<TapjackingInfo> {
return this.getRaspAndroidInfo("TAPJACKING")
}
/**
* Android specific: Get information about connected ADB.
*/
getAdbStatus(): Promise<boolean> {
return this.getRaspAndroidInfo("ADB_STATUS")
}
/**
* Android specific: Check if system screen lock (PIN or pattern) is being used to prevent
* unauthorized usage of the device by other people. It does not check if the device is currently locked.
*/
isScreenLockEnabled(): Promise<boolean> {
return this.getRaspAndroidInfo("SCREEN_LOCK")
}
/**
* Android specific: Check if Play Protect is enabled on the device. `undefined` value indicates that there was
* a problem obtaining the information.
*/
isPlayProtectEnabled(): Promise<boolean | undefined> {
return this.getRaspAndroidInfo("PLAY_PROTECT")
}
/**
* Android specific: Get information about screen readers.
*/
getScreenReaderInfo(): Promise<ScreenReaderInfo> {
return this.getRaspAndroidInfo("SCREEN_READER")
}
/**
* Android specific: Check if any not allowed screen reader is enabled on the device. Allowed screen readers are configured
* in `MalwarelyticsAndroidRaspScreenReadersConfig.allowedScreenReaders`.
*/
isNotAllowedScreenReaderEnabled(): Promise<boolean> {
return this.getRaspAndroidInfo("NA_SCREEN_READER")
}
/**
* Android specific: Check if there's a bad app that is able to create a system overlay. A bad app is one that
* has a treat index same or higher than `MalwarelyticsAndroidRaspTapjackingConfig.blockSensitivity`.
*/
isBadTapjackingCapableAppPresent(): Promise<boolean> {
return this.getRaspAndroidInfo("TAPJACKING_APP_PRESENT")
}
/**
* Android specific: Check if developer options are enabled on the device.
*/
isDeveloperOptionsEnabled(): Promise<boolean> {
return this.getRaspAndroidInfo("DEVELOPER_MODE")
}
/**
* Android specific: Obtain information about biometry on the device.
*/
getBiometryInfo(): Promise<BiometryInfo> {
return this.getRaspAndroidInfo("BIOMETRY")
}
/**
* Android specific: Obtain information about active call.
*/
getActiveCallInfo(): Promise<ActiveCallInfo> {
return this.getRaspAndroidInfo("ACTIVE_CALL")
}
// Private methods
/**
* Acquire typed information about RASP detection.
* @param messageType RASP message to get.
* @returns Value returned from native code.
*/
private async getRaspInfo<T>(messageType: RaspEventType): Promise<T> {
return await wrapNativeCall(this.module, (module) => module.getRaspInfo(messageType)) as T
}
/**
* Acquire typed information about RASP detection. This function fails if called on non-Apple platform.
* @param messageType RASP message to get.
* @returns Value returned from native code.
*/
private getRaspAppleInfo<T>(messageType: RaspEventType): Promise<T> {
if (Platform.OS != "ios") {
return Promise.reject(new MalwarelyticsError("METHOD_NOT_SUPPORTED", "This method is supported only on Apple platforms"))
}
return this.getRaspInfo(messageType)
}
/**
* Acquire typed information about RASP detection. This function fails if called on non-Android platform.
* @param messageType RASP message to get.
* @returns Value returned from native code.
*/
private getRaspAndroidInfo<T>(messageType: RaspEventType): Promise<T> {
if (Platform.OS != "android") {
return Promise.reject(new MalwarelyticsError("METHOD_NOT_SUPPORTED", "This method is supported only on Android platform"))
}
return this.getRaspInfo(messageType)
}
}
export interface MalwarelyticsRaspListener {
// Android + Apple
/**
* Called when debuger is attached to the executable.
*
* Platforms: Apple, Android
*
* @param detected True is debugger is attached.
*/
debuggerDetected(detected: boolean): void
/**
* Called when repackage is detected.
*
* Platforms: Apple, Android
*
* @param info Information about repackaging.
*/
repackagingDetected(info: RepackagingInfo): void
/**
* Called when device is jailbroken or rooted.
*
* Platforms: Apple, Android
*
* @param info Information about compromited system integrity.
*/
systemIntegrityCompromised(info: SystemIntegrityInfo): void
/**
* Called when application is running in emulator environment.
*
* Platforms: Apple, Android
*
* @param info Information about emulator environment.
*/
emulatorDetected(info: EmulatorInfo): void
/**
* Called when HTTP proxy is detected.
*
* Platforms: Apple (limited), Android
*
* @param info Information about proxy configuration.
*/
httpProxyDetected(info: HttpProxyInfo): void
/**
* Called when screen sharing or capturing is detected.
*
* Platforms: Apple, Android
*
* @param info Information about screen sharing.
*/
screenSharingDetected(info: ScreenSharingInfo): void
/**
* Called when VPN status is changed
*
* Platforms: Apple, Android
* @param active VPN status
*/
vpnDetected(active: boolean): void
// Android specific
/**
* Called when application detects a change in enabled screen readers.
*
* Platforms: Android
*
* @param info Information about detected screen readers.
*/
screenReaderDetected(info: ScreenReaderInfo): void
/**
* Called when tapjacking is detected.
*
* Platforms: Android
* @param info Information about detected tapjacking.
*/
tapjackingDetected(info: TapjackingInfo): void
/**
* Called when ADB status is changed. (TODO)
*
* Platforms: Android
*
* @param adbStatus ADB status changed
*/
adbStatusDetected(adbStatus: boolean): void
/**
* Call when active call detection changes are detected.
*
* Platforms: Android
*
* @param info Information about active call.
*/
activeCallDetected(info: ActiveCallInfo): void
/**
* Called when app presence changes.
*
* Platforms: Android
*
* @param info Information about app presence.
*/
appPresenceChangeDetected(info: AppPresenceInfo): void
// Apple specific
/**
* Called after user took the screenshot.
*
* Platforms: Apple
*/
userScreenshotDetected(): void
/**
* Called when reverse engineering tools detected on the system.
*
* Platforms: Apple
*/
reverseEngineeringToolsDetected(): void
/**
* Called when device passcode configuration is changed.
*
* Platforms: Apple
*
* @param enabled Passcode is enabled or disabled.
*/
systemPasscodeConfigurationChanged(enabled: boolean): void
/**
* Called when device's biometry configuration is changed.
*
* Platforms: Apple
*
* @param enabled Biometry is enabled or disabled.
*/
systemBiometryConfigurationChanged(enabled: boolean): void
/**
* Called when the status of phone call is changed.
*
* Platforms: Apple
*
* @param isOnCall `true` if there's active call right now.
*/
isOnCallChanged(isOnCall: boolean): void
}