From 38daa86bcf8e35a7fed22488661074e243e0c259 Mon Sep 17 00:00:00 2001 From: TEMFACK DERICK Date: Thu, 24 Oct 2024 22:59:30 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + Dockerfile | 25 ++++ README.md | 14 ++ app/.gitignore | 1 + app/__init__.py | 0 app/feature_extractor.py | 280 +++++++++++++++++++++++++++++++++++++++ app/main.py | 36 +++++ requirements.txt | 76 +++++++++++ 8 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/__init__.py create mode 100644 app/feature_extractor.py create mode 100644 app/main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5e96db --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b81fc76 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +# Use a specific, slim Python image +FROM python:3.11.4-slim + +# Set environment variables for optimal performance +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Create a non-root user +RUN adduser --disabled-password --gecos "" appuser + +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt + +WORKDIR /code +COPY ./app /code/app + +# Change ownership and switch to non-root user +RUN chown -R appuser:appuser /code +USER appuser + +# Expose port 80 +EXPOSE 8000 + +# Run Gunicorn with Uvicorn worker +CMD ["gunicorn", "app.main:app", "--workers", "3", "--worker-class", "uvicorn.workers.UvicornWorker","--timeout","3000", "--bind", "0.0.0.0:8000"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..749909c --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Build the docker image +``` +docker build -t tderick/android-malware-detection:latest . +``` + +# Run the image +``` +docker run -p 8080:8000 tderick/android-malware-detection:latest +``` + +# Push to docker hub +``` +docker push tderick/android-malware-detection:latest +``` \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..ba0430d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/feature_extractor.py b/app/feature_extractor.py new file mode 100644 index 0000000..b43d382 --- /dev/null +++ b/app/feature_extractor.py @@ -0,0 +1,280 @@ +from androguard.misc import AnalyzeAPK +from androguard.core.apk import APK + +features = { + 'onServiceConnected': 0, + 'bindService': 0, + 'attachInterface': 0, + 'ServiceConnection': 0, + 'android.os.Binder': 0, + 'SEND_SMS': 0, + 'Ljava.lang.Class.getCanonicalName': 0, + 'Ljava.lang.Class.getMethods': 0, + 'Ljava.lang.Class.cast': 0, + 'Ljava.net.URLDecoder': 0, + 'android.content.pm.Signature': 0, + 'android.telephony.SmsManager': 0, + 'READ_PHONE_STATE': 0, + 'getBinder': 0, + 'ClassLoader': 0, + 'Landroid.content.Context.registerReceiver': 0, + 'Ljava.lang.Class.getField': 0, + 'Landroid.content.Context.unregisterReceiver': 0, + 'GET_ACCOUNTS': 0, + 'RECEIVE_SMS': 0, + 'Ljava.lang.Class.getDeclaredField': 0, + 'READ_SMS': 0, + 'getCallingUid': 0, + 'Ljavax.crypto.spec.SecretKeySpec': 0, + 'android.intent.action.BOOT_COMPLETED': 0, + 'USE_CREDENTIALS': 0, + 'MANAGE_ACCOUNTS': 0, + 'android.content.pm.PackageInfo': 0, + 'KeySpec': 0, + 'TelephonyManager.getLine1Number': 0, + 'DexClassLoader': 0, + 'HttpGet.init': 0, + 'SecretKey': 0, + 'Ljava.lang.Class.getMethod': 0, + 'System.loadLibrary': 0, + 'android.intent.action.SEND': 0, + 'Ljavax.crypto.Cipher': 0, + 'WRITE_SMS': 0, + 'READ_SYNC_SETTINGS': 0, + 'AUTHENTICATE_ACCOUNTS': 0, + 'android.telephony.gsm.SmsManager': 0, + 'WRITE_HISTORY_BOOKMARKS': 0, + 'TelephonyManager.getSubscriberId': 0, + 'mount': 0, + 'INSTALL_PACKAGES': 0, + 'Runtime.getRuntime': 0, + 'CAMERA': 0, + 'Ljava.lang.Object.getClass': 0, + 'WRITE_SYNC_SETTINGS': 0, + 'READ_HISTORY_BOOKMARKS': 0, + 'Ljava.lang.Class.forName': 0, + 'INTERNET': 0, + 'android.intent.action.PACKAGE_REPLACED': 0, + 'Binder': 0, + 'android.intent.action.SEND_MULTIPLE': 0, + 'RECORD_AUDIO': 0, + 'IBinder': 0, + 'android.os.IBinder': 0, + 'createSubprocess': 0, + 'NFC': 0, + 'ACCESS_LOCATION_EXTRA_COMMANDS': 0, + 'URLClassLoader': 0, + 'WRITE_APN_SETTINGS': 0, + 'abortBroadcast': 0, + 'BIND_REMOTEVIEWS': 0, + 'android.intent.action.TIME_SET': 0, + 'READ_PROFILE': 0, + 'TelephonyManager.getDeviceId': 0, + 'MODIFY_AUDIO_SETTINGS': 0, + 'getCallingPid': 0, + 'READ_SYNC_STATS': 0, + 'BROADCAST_STICKY': 0, + 'android.intent.action.PACKAGE_REMOVED': 0, + 'android.intent.action.TIMEZONE_CHANGED': 0, + 'WAKE_LOCK': 0, 'RECEIVE_BOOT_COMPLETED': 0, + 'RESTART_PACKAGES': 0, + 'Ljava.lang.Class.getPackage': 0, + 'chmod': 0, + 'Ljava.lang.Class.getDeclaredClasses': 0, + 'android.intent.action.ACTION_POWER_DISCONNECTED': 0, + 'android.intent.action.PACKAGE_ADDED': 0, + 'PathClassLoader': 0, + 'TelephonyManager.getSimSerialNumber': 0, + 'Runtime.load': 0, + 'TelephonyManager.getCallState': 0, + 'BLUETOOTH': 0, + 'READ_CALENDAR': 0, + 'READ_CALL_LOG': 0, + 'SUBSCRIBED_FEEDS_WRITE': 0, + 'READ_EXTERNAL_STORAGE': 0, + 'TelephonyManager.getSimCountryIso': 0, + 'sendMultipartTextMessage': 0, + 'PackageInstaller': 0, + 'VIBRATE': 0, + 'remount': 0, + 'android.intent.action.ACTION_SHUTDOWN': 0, + 'sendDataMessage': 0, + 'ACCESS_NETWORK_STATE': 0, + 'chown': 0, + 'HttpPost.init': 0, + 'Ljava.lang.Class.getClasses': 0, + 'SUBSCRIBED_FEEDS_READ': 0, + 'TelephonyManager.isNetworkRoaming': 0, + 'CHANGE_WIFI_MULTICAST_STATE': 0, + 'WRITE_CALENDAR': 0, + 'android.intent.action.PACKAGE_DATA_CLEARED': 0, + 'MASTER_CLEAR': 0, + 'HttpUriRequest': 0, + 'UPDATE_DEVICE_STATS': 0, + 'WRITE_CALL_LOG': 0, + 'DELETE_PACKAGES': 0, + 'GET_TASKS': 0, + 'GLOBAL_SEARCH': 0, + 'DELETE_CACHE_FILES': 0, + 'WRITE_USER_DICTIONARY': 0, + 'android.intent.action.PACKAGE_CHANGED': 0, + 'android.intent.action.NEW_OUTGOING_CALL': 0, + 'REORDER_TASKS': 0, + 'WRITE_PROFILE': 0, + 'SET_WALLPAPER': 0, + 'BIND_INPUT_METHOD': 0, + 'divideMessage': 0, + 'READ_SOCIAL_STREAM': 0, + 'READ_USER_DICTIONARY': 0, + 'PROCESS_OUTGOING_CALLS': 0, + 'CALL_PRIVILEGED': 0, + 'Runtime.exec': 0, 'BIND_WALLPAPER': 0, + 'RECEIVE_WAP_PUSH': 0, + 'DUMP': 0, + 'BATTERY_STATS': 0, + 'ACCESS_COARSE_LOCATION': 0, + 'SET_TIME': 0, + 'android.intent.action.SENDTO': 0, + 'WRITE_SOCIAL_STREAM': 0, + 'WRITE_SETTINGS': 0, + 'REBOOT': 0, + 'BLUETOOTH_ADMIN': 0, + 'TelephonyManager.getNetworkOperator': 0, + '/system/bin': 0, + 'MessengerService': 0, + 'BIND_DEVICE_ADMIN': 0, + 'WRITE_GSERVICES': 0, + 'IRemoteService': 0, + 'KILL_BACKGROUND_PROCESSES': 0, + 'SET_ALARM': 0, + 'ACCOUNT_MANAGER': 0, + '/system/app': 0, + 'android.intent.action.CALL': 0, + 'STATUS_BAR': 0, + 'TelephonyManager.getSimOperator': 0, + 'PERSISTENT_ACTIVITY': 0, + 'CHANGE_NETWORK_STATE': 0, + 'onBind': 0, + 'Process.start': 0, + 'android.intent.action.SCREEN_ON': 0, + 'Context.bindService': 0, + 'RECEIVE_MMS': 0, + 'SET_TIME_ZONE': 0, + 'android.intent.action.BATTERY_OKAY': 0, + 'CONTROL_LOCATION_UPDATES': 0, + 'BROADCAST_WAP_PUSH': 0, + 'BIND_ACCESSIBILITY_SERVICE': 0, + 'ADD_VOICEMAIL': 0, + 'CALL_PHONE': 0, + 'ProcessBuilder': 0, + 'BIND_APPWIDGET': 0, + 'FLASHLIGHT': 0, + 'READ_LOGS': 0, + 'Ljava.lang.Class.getResource': 0, + 'defineClass': 0, + 'SET_PROCESS_LIMIT': 0, + 'android.intent.action.PACKAGE_RESTARTED': 0, + 'MOUNT_UNMOUNT_FILESYSTEMS': 0, + 'BIND_TEXT_SERVICE': 0, + 'INSTALL_LOCATION_PROVIDER': 0, + 'android.intent.action.CALL_BUTTON': 0, + 'android.intent.action.SCREEN_OFF': 0, + 'findClass': 0, + 'SYSTEM_ALERT_WINDOW': 0, + 'MOUNT_FORMAT_FILESYSTEMS': 0, + 'CHANGE_CONFIGURATION': 0, + 'CLEAR_APP_USER_DATA': 0, + 'intent.action.RUN': 0, + 'android.intent.action.SET_WALLPAPER': 0, + 'CHANGE_WIFI_STATE': 0, + 'READ_FRAME_BUFFER': 0, + 'ACCESS_SURFACE_FLINGER': 0, + 'Runtime.loadLibrary': 0, + 'BROADCAST_SMS': 0, + 'EXPAND_STATUS_BAR': 0, + 'INTERNAL_SYSTEM_WINDOW': 0, + 'android.intent.action.BATTERY_LOW': 0, + 'SET_ACTIVITY_WATCHER': 0, + 'WRITE_CONTACTS': 0, + 'android.intent.action.ACTION_POWER_CONNECTED': 0, + 'BIND_VPN_SERVICE': 0, + 'DISABLE_KEYGUARD': 0, + 'ACCESS_MOCK_LOCATION': 0, + 'GET_PACKAGE_SIZE': 0, + 'MODIFY_PHONE_STATE': 0, + 'CHANGE_COMPONENT_ENABLED_STATE': 0, + 'CLEAR_APP_CACHE': 0, + 'SET_ORIENTATION': 0, + 'READ_CONTACTS': 0, + 'DEVICE_POWER': 0, + 'HARDWARE_TEST': 0, + 'ACCESS_WIFI_STATE': 0, + 'WRITE_EXTERNAL_STORAGE': 0, + 'ACCESS_FINE_LOCATION': 0, + 'SET_WALLPAPER_HINTS': 0, + 'SET_PREFERRED_APPLICATIONS': 0, + 'WRITE_SECURE_SETTINGS': 0 +} + +class APKFeatureExtractor: + def __init__(self, apk_path): + self.apk_path = apk_path + self.features = features + self.dx = None + self.a = None + self.d = None + + def extract_features(self): + self.a, self.d, self.dx = AnalyzeAPK(self.apk_path) + self.extract_api_calls() + self.extract_permissions() + self.extract_intents() + self.extract_commands() + return self.features + + def extract_api_calls(self): + for method in self.dx.get_methods(): + for _, call, _ in method.get_xref_to(): + api_call = f'{call.class_name.strip(";").replace("/",".")}.{call.name}' + if api_call in self.features: + self.features[api_call] = 1 + + def extract_permissions(self): + permissions = self.a.get_permissions() + for perm in permissions: + perm = perm.split('.')[-1] + if perm in self.features: + self.features[perm] = 1 + + def extract_intents(self): + intents = self.extract_manifest_intents(self.apk_path) + for intent in intents: + if intent in self.features: + self.features[intent] = 1 + + def extract_commands(self): + for method in self.dx.get_methods(): + command = method.name + if command in self.features: + self.features[command] = 1 + + def extract_manifest_intents(self,apk_path): + # Load the APK + apk = APK(apk_path) + + # Get the AndroidManifest.xml + manifest_xml = apk.get_android_manifest_xml() + + # Parse the XML + root = apk.get_android_manifest_xml() + intents = [] + + # Extract intents from in , , , and + for component in ['activity', 'service', 'receiver', 'provider']: + for element in root.findall(f".//{component}"): + for intent_filter in element.findall(".//intent-filter"): + for action in intent_filter.findall(".//action"): + if action is not None and '{http://schemas.android.com/apk/res/android}name' in action.attrib: + intents.append(action.attrib['{http://schemas.android.com/apk/res/android}name']) + + return intents diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..0d86e37 --- /dev/null +++ b/app/main.py @@ -0,0 +1,36 @@ +import tempfile +import json +from fastapi import FastAPI, File, UploadFile, HTTPException, Response + +from .feature_extractor import APKFeatureExtractor + +app = FastAPI( + title="Android Malware Detection", + summary="Malware Detection API using Machine Learning", + description="This API is used to detect malware in Android applications using Machine Learning. Users have to submit APK file and the API will return the result of the detection (Malware or Benign).", + version="0.0.1", +) + + + +@app.post('/api/v1/android-malware-detection') +async def android_malware_detection(file: UploadFile = File(...)): + + if not file.filename.endswith(".apk"): + raise HTTPException(status_code=400, detail="Only APK files are allowed") + + # Create a temporary file in memory + with tempfile.NamedTemporaryFile(delete=False, suffix=".apk") as temp_file: + # Write the content of the uploaded file to the temporary file + content = await file.read() # Read the file content from UploadFile + temp_file.write(content) # Write the content to the temporary file + + # Get the path of the temporary file + temp_file_path = temp_file.name + + rs = APKFeatureExtractor(temp_file_path).extract_features() + + json_response = json.dumps(rs) + return Response(content=json_response,media_type="application/json") + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..012089a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,76 @@ +alembic==1.13.3 +androguard==4.1.2 +annotated-types==0.7.0 +anyio==4.6.2.post1 +apkInspector==1.3.2 +asn1crypto==1.5.1 +asttokens==2.4.1 +banal==1.0.6 +certifi==2024.8.30 +click==8.1.7 +colorama==0.4.6 +contourpy==1.3.0 +cycler==0.12.1 +dataset==1.6.2 +decorator==5.1.1 +dnspython==2.7.0 +email_validator==2.2.0 +executing==2.1.0 +fastapi==0.115.3 +fastapi-cli==0.0.5 +fonttools==4.54.1 +frida==16.5.5 +greenlet==3.1.1 +gunicorn==23.0.0 +h11==0.14.0 +httpcore==1.0.6 +httptools==0.6.4 +httpx==0.27.2 +idna==3.10 +ipython==8.28.0 +jedi==0.19.1 +Jinja2==3.1.4 +kiwisolver==1.4.7 +loguru==0.7.2 +lxml==5.3.0 +Mako==1.3.5 +markdown-it-py==3.0.0 +MarkupSafe==3.0.1 +matplotlib==3.9.2 +matplotlib-inline==0.1.7 +mdurl==0.1.2 +mutf8==1.0.6 +networkx==3.4 +numpy==2.1.2 +oscrypto==1.3.0 +packaging==24.1 +parso==0.8.4 +pexpect==4.9.0 +pillow==10.4.0 +prompt_toolkit==3.0.48 +ptyprocess==0.7.0 +pure_eval==0.2.3 +pydantic==2.9.2 +pydantic_core==2.23.4 +pydot==3.0.2 +Pygments==2.18.0 +pyparsing==3.1.4 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-multipart==0.0.12 +PyYAML==6.0.2 +rich==13.9.3 +shellingham==1.5.4 +six==1.16.0 +sniffio==1.3.1 +SQLAlchemy==1.4.54 +stack-data==0.6.3 +starlette==0.41.0 +traitlets==5.14.3 +typer==0.12.5 +typing_extensions==4.12.2 +uvicorn==0.32.0 +uvloop==0.21.0 +watchfiles==0.24.0 +wcwidth==0.2.13 +websockets==13.1