Technical Analysis of 送達 App

8 min readNov 23, 2023


Researchers: {alanh0, Byron, Darkfloyd, Jackson}

Recently, an online store claimed they could sell dog meat, and victim customers ordered, the shop would send a malicious Android App to the customer. Those victims claimed to have lost over 1M HKD because of the app.

Our friend {0xFF} pretended to be a customer and got this app, and our team carried out technical malware analysis.

Hashes MD5: a3bdfcd64b0c5fb82e6a5b608e0dacab; SHA-256: faf9d4f2352577e7e432602241fe5d934cd75873b5ca8a5b7109389b21821583


First of all, when installing and running the app, users have to relax the following restrictions and permissions:

  1. “Allow from unknown source”
  2. “Disable Google Play Protect”

3. “Allow restricted setting”

4. “Allow accessibility”


Besides, the app detects whether it’s running from an Emulator, which is a common malware technique to avoid dynamic analysis.

From the strings we check in the app `Emulator detected.\n this app does not support emulator devices`.

We search from the decompiled app, and we find that the keywords appear in two files: `bhuftjdmrnclghwxviksqzayazthmzytmqcpsjiscvbvjxlpuu31.smali` and `cuqzkbopfpglspmbsempbmkrkgdeesdubktfvkbihgdnouabcq14.smali`

The malware checks the `android.os.Build.MODEL`, `android.os.Build.PRODUCT`, `android.os.Build.MANUFACTURER`, etc. under the method isEmu_DIV_ID_lator()Z, to check whether the OS contains strings like Emulator, google_sdk, Genymotion, vbox86p, etc. the malware will not execute if the banned strings are detected.

We take Genymotion as the emulator, using adb shell to check the properties of the emulator and find that product name is vbox68p.

Bypassing such anti-emulator checking

From the above decompiled codes, we know that such anti-emulator checking is just a string matching. From Genymotion, the product name is “vbox86p”, thereby, the malicious app will reject to run on the emulator.

Thus, we make some modifications to set the product name to “helloworld” which will bypass such trivial checking.

As expected, we can launch the app successfully in Genymotion, and we can proceed with the following dynamic analysis.

Dynamic Analysis

We attach Genymotion to MobSF to do dynamic analysis, it is easier to detect the behavior once the app is executed. Capturing the network traffic, we can observe what kind of information the malware will steal.

Let us do some online shopping. To begin with, we have to register a new account, we can enter any garbage email address and phone number.

We purchase this item, and you can see the item price is $168.80 and total price is $25, it’s obviously suspicious.

bought this item

We continue to pay, and being redirected to a page containing list of banks. It asks for username, password and OTP.

After that, the app will be loading forever :)

Next, we attempt to make a second purchase and use another bank, we realize that the order number is always the same.

We check the URLs captured from Dynamic Analysis, the bank credential is definitely being captured and sent to the attacker server instead of the bank itself.


Here is the video recording of executing the app and showing the flow how the victim information is stolen:

Static Analysis

Once we learn from behavior and flow from the above dynamic analysis we can proceed with static analysis over the malicious app without executing it.

Manifest XML Analysis

From the manifest.xml files, it requires the app to obtain excessive permissions to run. Interestingly, we can find permissions targeting Oppo and Huawei Android devices.

    <uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BIND_WALLPAPER" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BACKGROUND_ACTIVITY_STARTER" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="oppo.permission.OPPO_COMPONENT_SAFE" />
<uses-permission android:name="com.huawei.permission.external_app_settings.USE_COMPONENT" />
<uses-permission android:name="android.permission.INTERNET" />

And it will capture the Whatsapp OTP code.

        <package android:name="" />
<action android:name="com.whatsapp.action.INSTRUMENTATION_CALLBACK_SERVICE" />
<action android:name="whatsapp.payments.intent.action.STEP_UP" />
<package android:name="" />
<package android:name="" />
<package android:name="com.whatsapp.w4b" />
<action android:name="com.whatsapp.otp.OTP_RETRIEVED" />

Launching the App

The app starts with the following code in the main routine:

        <activity android:theme="@android:style/Theme.Translucent.NoTitleBar" n1:"@null" n7:compileSdkVersion="@null" android:APKTOOL_MISSING_0="@null" n8:APKTOOL_MISSING_0="@null" n9:="" android:exported="true" n1:"" n10:compileSdkVersion="@null" xmlns:n7="送達百貨"
xmlns:n8="cleveland.considers.rblnyttyuslvorimqiywxgefcxxbsyhpqhhuinbdaqrqcubgpv2.bhuftjdmrnclghwxviksqzayazthmzytmqcpsjiscvbvjxlpuu31" xmlns:n9="enabled" xmlns:n10="minSdkVersion">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

Let us dive into: cleveland.considers.rblnyttyuslvorimqiywxgefcxxbsyhpqhhuinbdaqrqcubgpv2.bhuftjdmrnclghwxviksqzayazthmzytmqcpsjiscvbvjxlpuu31:

App Design and Flow

The code converted from APK is Smali, an assembly language used in the Dalvik virtual machine in Android operating systems. In addition, we have decompiled the APK into Java code. Here are some highlights based on the provided Smali and Java code:

1. **Class and Superclass**: The class defined in the provided code is `bhuftjdmrnclghwxviksqzayazthmzytmqcpsjiscvbvjxlpuu31` and it’s located in the `cleveland/considers/rblnyttyuslvorimqiywxgefcxxbsyhpqhhuinbdaqrqcubgpv2` package. This class extends the `Activity` superclass, indicating that it’s an Android activity, an essential building block of an Android app.

2. **Annotations**: The class has some annotations that define its member classes, `MyWebViewClient` and `MyChrome`.

3. **Fields**: Various fields are defined in this class, such as `mUploadMessage`, `mWebView`, `value`, etc. These fields might be used to hold state or data for the class.

4. **Methods**: The class has several methods, including `AskDraw()` and the constructor `<init>()`. The constructor initializes fields like `out`, `Oklistner`, and `value`. The `AskDraw()` method creates a dialog box with different messages based on the user’s language settings.

5. **Localization**: The `AskDraw()` method contains code for localization based on the user’s language. It appears to support English, Arabic, Chinese, and Turkish.

6. **Web Interaction**: The class seems to interact with a WebView (as suggested by the `mWebView` field and `MyWebViewClient` member class), which is a view that displays web pages.

We delve a bit deeper into the details of the provided Smali and decompiled Java code:

1. **Fields**:

- `FILECHOOSER_RESULTCODE` and `REQUEST_SET_DEFAULT_DIALER`: These are static final integer fields, likely used for request codes in the application, possibly related to file selection and setting the default dialer functionality.

- `forautostart`: A public static boolean likely used to check or control whether a particular action or service should automatically start.

- `Oklistner` and `out`: These are instances of `View.OnClickListener`, suggesting that they’re used to handle click events on views in the Android application.

- `mUploadMessage`: This field is of the type `ValueCallback<Uri[]>`, indicating that it’s used to handle a callback function with an array of Uri objects, likely related to file upload functionality in a web view.

- `mWebView`: An instance of `WebView`, a view that displays web pages. This could be used to load web pages or JavaScript in the application.

  • `value`: A string field, initialized with a URL “https://app[.]hkliveshop[.]shop/" in the constructor `<init>()`. It could be the default URL to be loaded in the WebView. We will display more details about this URL in the Finding section.

2. **Methods**:

- The constructor `<init>()` is called when an object of the class is created. It initializes `out`, `Oklistner`, and `value` fields. The `out` and `Oklistner` are initialized with instances of (presumably) inner classes `bhuftjdmrnclghwxviksqzayazthmzytmqcpsjiscvbvjxlpuu31$4` and `bhuftjdmrnclghwxviksqzayazthmzytmqcpsjiscvbvjxlpuu31$5` respectively, likely defining specific click behaviors.

- The `AskDraw()` method, from the part of the code shown, appears to be responsible for creating a language-specific alert dialog box. The language is determined by getting the default locale and checking its language. The languages in the provided code are English, Arabic, Chinese, and Turkish.

3. **Inner Classes**:

  • The `MyWebViewClient` and `MyChrome` inner classes are declared in the class annotations. They are not defined in the provided code but likely extend `WebViewClient` and `WebChromeClient` respectively. `WebViewClient` handles various aspects of loading web content, and `WebChromeClient` handles JavaScript dialogs, favicons, titles, and progress.


With a quick string search and during the previous review of the mentioned Java / Smali program:

We can identify a URL (https://app[.]hkliveshop[.]shop), an online store selling different meats, including dog meats. The attacker could be using Tencent Cloud to host the malicious servers. With reverse IP lookup, we can discover more similar domains.

Whois record of
Domain information and location verified with Shodan
Reverse IP Lookup to discover three examples of domains hosted under the same server

From VirusTotal, the URL relates to several malicious Android apps spread out since July 2023. Here is the graph relation of the target online store domain.

VT Graph: Relation between malicious files and target online shop

It is rather interesting, this server also target to launch SSH brute force attack against other servers.

Interestingly, Tencent cloud should be registered with genuine registrant information, we believe this information can be traceable to the attacker, and will report this attack case to JPCERT in Japan for follow-up.

Launching SSH attacks against other servers


This app is not purely for scamming. It attempts to compromise several critical information and authentication details from the victims, and we can conclude this malicious app is quite sophisticatedly made for the attack purpose. As we can see from dynamic analysis, the malware requests for full control of the phone, when placing the orders, users have to enter their bank username, password, and OTP, which will be sent to the malware server and bank credentials will be stolen.

VXRL is an animal-friendly organization, that supports and protects animals’ lives, we condemn any activities that are hazardous to their lives.




VXRL Team is founded by group of enthusiastic security researchers, providing information security services and contribute to the community.