diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53c5262 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/.vscode/ +**/target/ +**/build/ +**/bin/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aa36aa2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 iamywang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2160d46 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# EavesDroid: Eavesdropping User Behaviors via OS Side Channels on Smartphones + +This repo contains the source code and dataset of our paper "EavesDroid: Eavesdropping User Behaviors via OS Side Channels on Smartphones" published in IEEE Internet of Things Journal (IoT-J). + +![EavesDroid](./overview.png) + +## 0x01 Getting Started + +Prerequisites: + +- Android Studio +- Android NDK +- Python 3.11 + - numpy + - matplotlib + - keras + - django + - cydtw + +## 0x02 Repo Structure + +**1. Android App** + +- `app/` contains the source code of our Android app. + +- Please rename it to `Sampler` and import it into Android Studio to build the app. + +- Note: `val api = "http://192.168.1.2:8000"` in `MainActivity.kt` should be changed to the proper server address. + +**2. Server** + +- `backend/` contains the source code of our Django server. + +- Please run `python manage.py runserver` to start the server. + +**3. Data Collection** + +- `collection/` contains the source code of emulated data collection tool. + +- Please run `python sample.py/sample2.py` to collect data from your own devices. + +- Note: parameters should be changed to the proper values. + +**4. Data Classification** + +- `classification/` contains the source code of user behavior classification tool. + +- `all_model.py` contains the baseline models used in our paper: 1D-CNN, LSTM, GRU. + +- `dtw_model.py` contains the DTW-KNN algorithm used in our paper. + +- `classify.py` is the main script to infer user behaviors with our CNN-GRU model. + +**4. Dataset** + +- `dataset/` contains the dataset used in our paper. + +- Data in this directory can be used to train classification models and reproduce our results. + +**5. Figures** + +- `figures/` contains the figure generation scripts used in our paper. + +- Files in this directory can be used to reproduce the figures in our paper. + +## 0x03 Copyright and License + +This project is licensed under the terms of the MIT License. + +## 0x04 Contact and Citation + +If you have any questions, please contact me through `GitHub Issues` or email: wangquancheng@whu.edu.cn. + +If our work is useful for your research, please consider citing our paper: + +```bibtex +@ARTICLE{wang2024eavesdroid, + author={Wang, Quancheng and Tang, Ming and Fu, Jianming}, + journal={IEEE Internet of Things Journal (IoT-J)}, + title={EavesDroid: Eavesdropping User Behaviors via OS Side Channels on Smartphones}, + year={2024}, + volume={11}, + number={3}, + pages={3979-3993}, + doi={10.1109/JIOT.2023.3298992} +} +``` diff --git a/app/.gitignore b/app/.gitignore new file mode 100755 index 0000000..57a997f --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,18 @@ +*.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 +build/ +.idea + diff --git a/app/app/.gitignore b/app/app/.gitignore new file mode 100755 index 0000000..42afabf --- /dev/null +++ b/app/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/app/build.gradle b/app/app/build.gradle new file mode 100755 index 0000000..2a33df3 --- /dev/null +++ b/app/app/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 33 + + defaultConfig { + applicationId "com.iamywang.sampler" + minSdk 29 + targetSdk 33 + versionCode 22070709 + versionName "1.1.20220707" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags '-std=c++11' + } + } + + ndk { + moduleName "app" + abiFilters "arm64-v8a" + } + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + version '3.18.1' + } + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.8.0' + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'com.squareup.okhttp3:okhttp:4.9.3' + implementation 'com.alibaba:fastjson:1.2.83' +} \ No newline at end of file diff --git a/app/app/proguard-rules.pro b/app/app/proguard-rules.pro new file mode 100755 index 0000000..481bb43 --- /dev/null +++ b/app/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/app/src/androidTest/java/com/iamywang/sampler/ExampleInstrumentedTest.kt b/app/app/src/androidTest/java/com/iamywang/sampler/ExampleInstrumentedTest.kt new file mode 100755 index 0000000..4b79692 --- /dev/null +++ b/app/app/src/androidTest/java/com/iamywang/sampler/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.iamywang.sampler + +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.iamywang.sampler", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..70e7aec --- /dev/null +++ b/app/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/app/src/main/cpp/CMakeLists.txt b/app/app/src/main/cpp/CMakeLists.txt new file mode 100755 index 0000000..0b8dc4b --- /dev/null +++ b/app/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,48 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.18.1) + +# Declares and names the project. + +project("sampler") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + sampler + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + native-lib.cpp) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + sampler + + # Links the target library to the log library + # included in the NDK. + ${log-lib}) \ No newline at end of file diff --git a/app/app/src/main/cpp/native-lib.cpp b/app/app/src/main/cpp/native-lib.cpp new file mode 100755 index 0000000..5a109a3 --- /dev/null +++ b/app/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,58 @@ +// ============================================================================ +// This file is part of EavesDroid. +// +// Author: iamywang +// Date Created: Jan 27, 2024 +// ============================================================================ +#include +#include +#include +#include +#include + +extern "C" JNIEXPORT jint JNICALL +Java_com_iamywang_sampler_BackService_sysinfo_1procs(JNIEnv *env, jobject thiz) { + struct sysinfo info{}; + sysinfo(&info); + return info.procs; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_iamywang_sampler_BackService_statvfs_1f_1bavail(JNIEnv *env, jobject thiz) { + struct statvfs st{}; + statvfs("/sdcard", &st); + return st.f_bavail; +} + +extern "C" +JNIEXPORT jlong JNICALL +Java_com_iamywang_sampler_BackService_sysconf_1avphys_1pages(JNIEnv *env, jobject thiz) { + return sysconf(_SC_AVPHYS_PAGES); +} + +extern "C" +JNIEXPORT jlong JNICALL +Java_com_iamywang_sampler_BackService_statvfs_1f_1ffree(JNIEnv *env, jobject thiz) { + struct statvfs st{}; + statvfs("/sdcard", &st); + return st.f_ffree; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_iamywang_sampler_BackService_sysinfo_1freeram(JNIEnv *env, jobject thiz) { + struct sysinfo info{}; + sysinfo(&info); + return info.freeram; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_iamywang_sampler_BackService_sysinfo_1sharedram(JNIEnv *env, jobject thiz) { + struct sysinfo info{}; + sysinfo(&info); + return info.sharedram; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_iamywang_sampler_BackService_get_1avphys_1pages(JNIEnv *env, jobject thiz) { + return get_avphys_pages(); +} diff --git a/app/app/src/main/ic_launcher-playstore.png b/app/app/src/main/ic_launcher-playstore.png new file mode 100755 index 0000000..11c5334 Binary files /dev/null and b/app/app/src/main/ic_launcher-playstore.png differ diff --git a/app/app/src/main/java/com/iamywang/sampler/BackService.kt b/app/app/src/main/java/com/iamywang/sampler/BackService.kt new file mode 100755 index 0000000..289a139 --- /dev/null +++ b/app/app/src/main/java/com/iamywang/sampler/BackService.kt @@ -0,0 +1,129 @@ +// ============================================================================ +// This file is part of EavesDroid. +// +// Author: iamywang +// Date Created: Jan 27, 2024 +// ============================================================================ +package com.iamywang.sampler + +import android.app.Service +import android.content.Intent +import android.os.Build +import android.os.IBinder +import com.alibaba.fastjson.JSON +import com.alibaba.fastjson.JSONObject +import java.util.* + +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException + + +class BackService : Service() { + + private external fun sysinfo_procs(): Int + private external fun statvfs_f_bavail(): Long + private external fun sysconf_avphys_pages(): Long + private external fun statvfs_f_ffree(): Long + private external fun sysinfo_freeram(): Long + private external fun sysinfo_sharedram(): Long + private external fun get_avphys_pages(): Long + + + override fun onBind(arg0: Intent): IBinder? { + return null + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + // new Thread + val thread = Thread { + val time = Date().toString() + val total = 5000L + val init = System.currentTimeMillis() + var i = 0L + + var vs1 = "" + var vs2 = "" + var vs3 = "" + var vs4 = "" + var vs5 = "" + // var vs6 = "" + // var vs7 = "" + + val model = Build.MODEL + val manufacturer = Build.MANUFACTURER + val brand = Build.BRAND + val version = Build.VERSION.RELEASE + val sdk = Build.VERSION.SDK_INT + + while (true) { + val t = System.currentTimeMillis() - init + if (t > i) { + val v1 = sysinfo_procs() + val v2 = statvfs_f_bavail() + val v3 = sysconf_avphys_pages() + val v4 = statvfs_f_ffree() + val v5 = sysinfo_freeram() + // val v6 = sysinfo_sharedram() + // val v7 = get_avphys_pages() + + vs1 += "$v1," + vs2 += "$v2," + vs3 += "$v3," + vs4 += "$v4," + vs5 += "$v5," + // vs6 += "$v6," + // vs7 += "$v7," + i++ + + if (t > total - 1) { + vs1 += "$v1" + vs2 += "$v2" + vs3 += "$v3" + vs4 += "$v4" + vs5 += "$v5" + // vs6 += "$v6" + // vs7 += "$v7" + + val api = "http://10.201.170.123:8000" + val obj = JSONObject() + obj["model"] = "$manufacturer $brand $model" + obj["version"] = "$version $sdk" + obj["time"] = time + obj["vs1"] = vs1 + obj["vs2"] = vs2 + obj["vs3"] = vs3 + obj["vs4"] = vs4 + obj["vs5"] = vs5 + // obj["vs6"] = vs6 + // obj["vs7"] = vs7 + + val okHttpClient = OkHttpClient() + val mediaType = "application/json; charset=utf-8".toMediaType() + val requestBody = JSON.toJSONString(obj).toRequestBody(mediaType) + val request = Request.Builder() + .url("$api/upload/") + .post(requestBody) + .build() + okHttpClient.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + println("failed") + } + + override fun onResponse(call: Call, response: Response) { + println("success") + } + }) + break + } + } + } + } + + // start thread + thread.start() + + return START_STICKY + } +} diff --git a/app/app/src/main/java/com/iamywang/sampler/ListItem.kt b/app/app/src/main/java/com/iamywang/sampler/ListItem.kt new file mode 100755 index 0000000..deb8aeb --- /dev/null +++ b/app/app/src/main/java/com/iamywang/sampler/ListItem.kt @@ -0,0 +1,23 @@ +// ============================================================================ +// This file is part of EavesDroid. +// +// Author: iamywang +// Date Created: Jan 27, 2024 +// ============================================================================ +package com.iamywang.sampler + +class ListItem constructor( + id: Int, + title: String, + time: String +) { + var id = 0 + var title = "" + var time = "" + + init { + this.id = id + this.title = title + this.time = time + } +} \ No newline at end of file diff --git a/app/app/src/main/java/com/iamywang/sampler/ListItemAdapter.java b/app/app/src/main/java/com/iamywang/sampler/ListItemAdapter.java new file mode 100755 index 0000000..17ce9af --- /dev/null +++ b/app/app/src/main/java/com/iamywang/sampler/ListItemAdapter.java @@ -0,0 +1,56 @@ +// ============================================================================ +// This file is part of EavesDroid. +// +// Author: iamywang +// Date Created: Jan 27, 2024 +// ============================================================================ +package com.iamywang.sampler; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import java.util.LinkedList; + +public class ListItemAdapter extends BaseAdapter { + private final LinkedList mData; + private final Context mContext; + + public ListItemAdapter(LinkedList mData, Context mContext) { + this.mData = mData; + this.mContext = mContext; + } + + @Override + public int getCount() { + return mData.size(); + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false); + + TextView item_id = convertView.findViewById(R.id.list_number); + TextView item_title = convertView.findViewById(R.id.list_title); + TextView item_time = convertView.findViewById(R.id.list_time); + + item_id.setText(String.valueOf(mData.get(position).getId())); + item_title.setText(mData.get(position).getTitle()); + item_time.setText(mData.get(position).getTime()); + + return convertView; + } +} diff --git a/app/app/src/main/java/com/iamywang/sampler/MainActivity.kt b/app/app/src/main/java/com/iamywang/sampler/MainActivity.kt new file mode 100755 index 0000000..d10770b --- /dev/null +++ b/app/app/src/main/java/com/iamywang/sampler/MainActivity.kt @@ -0,0 +1,83 @@ +// ============================================================================ +// This file is part of EavesDroid. +// +// Author: iamywang +// Date Created: Jan 27, 2024 +// ============================================================================ +package com.iamywang.sampler + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.View +import android.widget.ListView +import androidx.appcompat.app.AppCompatActivity +import com.alibaba.fastjson.JSON +import com.alibaba.fastjson.JSONObject +import com.iamywang.sampler.databinding.ActivityMainBinding +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import java.util.* + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + + private var globalList = LinkedList() + + private val model = Build.MODEL + private val manufacturer = Build.MANUFACTURER + private val brand = Build.BRAND + private val version = Build.VERSION.RELEASE + private val sdk = Build.VERSION.SDK_INT + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + globalList.add(ListItem(1, "$manufacturer $brand $model", Date().toString())) + globalList.add(ListItem(2, "Android $version (SDK $sdk)", Date().toString())) + + setList(globalList, binding.mainList) + + val service = Intent(baseContext, BackService::class.java) + startService(service) + } + + fun trainNetwork(view: View) { + val api = "http://192.168.1.2:8000" + val obj = JSONObject() + obj["model"] = "$manufacturer $brand $model" + obj["version"] = "$version $sdk" + + val okHttpClient = OkHttpClient() + val mediaType = "application/json; charset=utf-8".toMediaType() + val requestBody = JSON.toJSONString(obj).toRequestBody(mediaType) + val request = Request.Builder() + .url("$api/train/") + .post(requestBody) + .build() + okHttpClient.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + println("failed") + } + + override fun onResponse(call: Call, response: Response) { + println("success") + } + }) + } + + private fun setList(list: LinkedList, listView: ListView) { + val mAdapter = ListItemAdapter(list, this) + listView.adapter = mAdapter + } + + companion object { + init { + System.loadLibrary("sampler") + } + } +} \ No newline at end of file diff --git a/app/app/src/main/res/drawable-v24/ic_number.xml b/app/app/src/main/res/drawable-v24/ic_number.xml new file mode 100755 index 0000000..99fee59 --- /dev/null +++ b/app/app/src/main/res/drawable-v24/ic_number.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/app/src/main/res/drawable-v24/ic_time.xml b/app/app/src/main/res/drawable-v24/ic_time.xml new file mode 100755 index 0000000..df70f03 --- /dev/null +++ b/app/app/src/main/res/drawable-v24/ic_time.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100755 index 0000000..9cf9a0e --- /dev/null +++ b/app/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/app/src/main/res/drawable/ic_spy.xml b/app/app/src/main/res/drawable/ic_spy.xml new file mode 100755 index 0000000..80ec722 --- /dev/null +++ b/app/app/src/main/res/drawable/ic_spy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/app/src/main/res/layout/activity_main.xml b/app/app/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000..6299a48 --- /dev/null +++ b/app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + +