mobsec-modules-library 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Stormtrooperroman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: mobsec_modules_library
3
+ Version: 0.0.1
4
+ Summary: Library for writing MobSec Modules using Python
5
+ Author-email: Stormtrooperroman <stormtrooperroman@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Stormtrooperroman/mobsec_modules_library
8
+ Project-URL: Issues, https://github.com/Stormtrooperroman/mobsec_modules_library/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: redis>=4.0.2
15
+ Requires-Dist: fastapi>=0.68.0
16
+ Requires-Dist: uvicorn>=0.15.0
17
+ Dynamic: license-file
18
+
19
+ # MobSec modules library
20
+
21
+ Library to help write modules for [MobSec](https://github.com/Stormtrooperroman/MobSec)
@@ -0,0 +1,3 @@
1
+ # MobSec modules library
2
+
3
+ Library to help write modules for [MobSec](https://github.com/Stormtrooperroman/MobSec)
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "mobsec_modules_library"
3
+ version = "0.0.1"
4
+ authors = [
5
+ { name="Stormtrooperroman", email="stormtrooperroman@gmail.com" },
6
+ ]
7
+ description = "Library for writing MobSec Modules using Python"
8
+ readme = "README.md"
9
+ requires-python = ">=3.9"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICEN[CS]E*"]
16
+
17
+ dependencies = [
18
+ "redis>=4.0.2",
19
+ "fastapi>=0.68.0",
20
+ "uvicorn>=0.15.0"
21
+
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/Stormtrooperroman/mobsec_modules_library"
26
+ Issues = "https://github.com/Stormtrooperroman/mobsec_modules_library/issues"
27
+
28
+ [build-system]
29
+ requires = ["setuptools >= 77.0.3"]
30
+ build-backend = "setuptools.build_meta"
31
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ from .base_websocket_manager import BaseWebSocketManager
2
+ from .base_module_manager import BaseModuleManager
3
+ from .base_server import create_module_app, run_module_server
4
+
5
+ __all__ = [
6
+ "BaseWebSocketManager",
7
+ "BaseModuleManager",
8
+ "create_module_app",
9
+ "run_module_server",
10
+ ]
@@ -0,0 +1,114 @@
1
+ """ADB utilities"""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ from typing import Dict, List, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def get_adb_env() -> Dict[str, str]:
12
+ return os.environ.copy()
13
+
14
+
15
+ async def execute_adb_command(
16
+ device_id: str,
17
+ command: List[str],
18
+ env: Optional[Dict[str, str]] = None,
19
+ ) -> tuple[str, str, int]:
20
+ try:
21
+ adb_cmd = ["adb", "-H", "backend"]
22
+ if device_id:
23
+ adb_cmd.extend(["-s", device_id])
24
+ adb_cmd.extend(command)
25
+
26
+ process = await asyncio.create_subprocess_exec(
27
+ *adb_cmd,
28
+ stdout=asyncio.subprocess.PIPE,
29
+ stderr=asyncio.subprocess.PIPE,
30
+ env=env,
31
+ )
32
+ stdout, stderr = await process.communicate()
33
+
34
+ logger.info(stderr)
35
+ return stdout.decode(), stderr.decode(), process.returncode
36
+ except Exception as e:
37
+ logger.error("Error executing ADB command: %s", str(e))
38
+ return "", str(e), -1
39
+
40
+
41
+ async def execute_adb_shell(
42
+ device_id: str,
43
+ shell_command: str,
44
+ env: Optional[Dict[str, str]] = None,
45
+ ) -> tuple[str, str, int]:
46
+ return await execute_adb_command(
47
+ device_id=device_id,
48
+ command=["shell", shell_command],
49
+ env=env,
50
+ )
51
+
52
+
53
+ async def remove_all_port_forwarding(
54
+ device_id: Optional[str] = None,
55
+ ) -> tuple[str, str, int]:
56
+ return await execute_adb_command(
57
+ device_id=device_id,
58
+ command=["forward", "--remove-all"],
59
+ env=None,
60
+ )
61
+
62
+
63
+ async def check_su_availability(device_id: str) -> bool:
64
+ """
65
+ Check su availability on device
66
+
67
+ Args:
68
+ device_id: Device ID
69
+
70
+ Returns:
71
+ True if su is available, False otherwise
72
+ """
73
+ try:
74
+ env = get_adb_env()
75
+
76
+ # First check if su exists
77
+ stdout, _, return_code = await execute_adb_shell(
78
+ device_id=device_id,
79
+ shell_command="which su",
80
+ env=env,
81
+ )
82
+
83
+ if return_code != 0:
84
+ logger.debug("su not found on device")
85
+ return False
86
+
87
+ # Check su functionality
88
+ stdout, _, return_code = await execute_adb_shell(
89
+ device_id=device_id,
90
+ shell_command='echo "echo SU_WORKS" | timeout 5 su 2>/dev/null || echo "SU_FAILED"',
91
+ env=env,
92
+ )
93
+
94
+ output = stdout.strip()
95
+
96
+ if "SU_WORKS" in output:
97
+ logger.debug("su is available and working")
98
+ return True
99
+
100
+ # Alternative check
101
+ stdout, _, return_code = await execute_adb_shell(
102
+ device_id=device_id,
103
+ shell_command='echo "exit" | su 2>/dev/null && echo "SU_SIMPLE_WORKS" || echo "SU_SIMPLE_FAILED"',
104
+ env=env,
105
+ )
106
+ simple_output = stdout.strip()
107
+
108
+ available = "SU_SIMPLE_WORKS" in simple_output
109
+ logger.debug("su availability: %s", available)
110
+ return available
111
+
112
+ except Exception as e:
113
+ logger.error("Error checking su availability: %s", str(e))
114
+ return False
@@ -0,0 +1,18 @@
1
+ from abc import ABC, abstractmethod
2
+ from fastapi import WebSocket
3
+
4
+
5
+ class BaseModuleManager(ABC):
6
+
7
+ @abstractmethod
8
+ async def start(self) -> bool:
9
+ raise NotImplementedError
10
+
11
+ @abstractmethod
12
+ async def handle_message(
13
+ self, websocket: WebSocket, device_id: str, text: str
14
+ ) -> None:
15
+ raise NotImplementedError
16
+
17
+ async def stop(self) -> None:
18
+ return None
@@ -0,0 +1,62 @@
1
+ import logging
2
+ import os
3
+ from contextlib import asynccontextmanager
4
+ from typing import Awaitable, Callable, Optional
5
+
6
+ import uvicorn
7
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
8
+
9
+ from .base_module_manager import BaseModuleManager
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ ManagerFactory = Callable[[WebSocket, str], Awaitable[BaseModuleManager]]
14
+ LifespanInit = Callable[[], Awaitable[None]]
15
+
16
+
17
+ def create_module_app(
18
+ title: str,
19
+ manager_factory: ManagerFactory,
20
+ lifespan_init: Optional[LifespanInit] = None,
21
+ ) -> FastAPI:
22
+
23
+ @asynccontextmanager
24
+ async def lifespan(app: FastAPI):
25
+ if lifespan_init is not None:
26
+ await lifespan_init()
27
+ yield
28
+
29
+ app = FastAPI(title=title, lifespan=lifespan)
30
+
31
+ @app.websocket("/ws/{device_id}")
32
+ async def websocket_endpoint(websocket: WebSocket, device_id: str):
33
+ await websocket.accept()
34
+ logger.info("%s WebSocket connected for device %s", title, device_id)
35
+
36
+ manager = await manager_factory(websocket, device_id)
37
+ if not await manager.start():
38
+ await websocket.close(code=4000, reason=f"Failed to start {title} manager")
39
+ return
40
+
41
+ try:
42
+ while True:
43
+ message = await websocket.receive()
44
+ if message["type"] == "websocket.disconnect":
45
+ break
46
+ if message["type"] == "websocket.receive" and "text" in message:
47
+ await manager.handle_message(websocket, device_id, message["text"])
48
+ except WebSocketDisconnect:
49
+ logger.info("%s WebSocket disconnected for device %s", title, device_id)
50
+ except Exception as e:
51
+ logger.error(
52
+ "Error in %s session for device %s: %s", title, device_id, str(e)
53
+ )
54
+ finally:
55
+ await manager.stop()
56
+
57
+ return app
58
+
59
+
60
+ def run_module_server(app: FastAPI, default_port: int) -> None:
61
+ port = int(os.getenv("PORT", str(default_port)))
62
+ uvicorn.run(app, host="0.0.0.0", port=port)
@@ -0,0 +1,29 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Dict
4
+
5
+ from fastapi import WebSocket
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class BaseWebSocketManager:
11
+ """Base class for managers working with WebSocket."""
12
+
13
+ def __init__(self, websocket: WebSocket, manager_type: str):
14
+ self.websocket = websocket
15
+ self.manager_type = manager_type
16
+
17
+ async def send_response(self, data: Dict[str, Any]):
18
+ try:
19
+ if self.websocket.client_state.CONNECTED:
20
+ await self.websocket.send_text(json.dumps(data))
21
+ else:
22
+ logger.warning("WebSocket not connected, cannot send response")
23
+ except Exception as e:
24
+ logger.error("Error sending response: %s", str(e))
25
+
26
+ async def send_error(self, message: str, error_type: str = "error"):
27
+ await self.send_response(
28
+ {"type": self.manager_type, "action": error_type, "message": message}
29
+ )
@@ -0,0 +1,88 @@
1
+ import redis
2
+ import json
3
+ import os
4
+ import asyncio
5
+ from typing import Dict, Any, List
6
+ import logging
7
+
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format="%(asctime)s - %(levelname)s - %(message)s",
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class StaticModule:
17
+ def __init__(self, module_name: str):
18
+ self.redis_client = redis.Redis.from_url(
19
+ os.getenv("REDIS_URL"), decode_responses=True
20
+ )
21
+ self.module_name = module_name
22
+
23
+ async def start(self):
24
+ logger.info(f"Starting {self.module_name} module...")
25
+ while True:
26
+ try:
27
+ queue_key = f"module:{self.module_name}:queue"
28
+ task_id = self.redis_client.lpop(queue_key)
29
+
30
+ if task_id:
31
+ try:
32
+ task_data_str = self.redis_client.get(f"task:{task_id}")
33
+ if not task_data_str:
34
+ logger.error(f"Task data not found for task_id: {task_id}")
35
+ continue
36
+
37
+ task_data = json.loads(task_data_str)
38
+ logger.info(
39
+ f"Processing task {task_id} for file: {task_data.get('file_hash')}"
40
+ )
41
+
42
+ result = await self.process(task_data)
43
+ if not result:
44
+ logger.error(
45
+ f"No result returned from process for task {task_id}"
46
+ )
47
+ continue
48
+
49
+ # Store result
50
+ result_key = (
51
+ f"result:{self.module_name}:{task_data['file_hash']}"
52
+ )
53
+ self.redis_client.set(result_key, json.dumps(result))
54
+ logger.info(f"Successfully stored result for task {task_id}")
55
+
56
+ except json.JSONDecodeError as e:
57
+ logger.error(
58
+ f"Failed to parse task data for task {task_id}: {str(e)}"
59
+ )
60
+ except redis.RedisError as e:
61
+ logger.error(
62
+ f"Redis error while processing task {task_id}: {str(e)}"
63
+ )
64
+ except Exception as e:
65
+ import traceback
66
+
67
+ logger.error(f"Error processing task {task_id}: {str(e)}")
68
+ logger.error(f"Traceback: {traceback.format_exc()}")
69
+
70
+ await asyncio.sleep(1)
71
+
72
+ except Exception as e:
73
+ import traceback
74
+
75
+ logger.error(
76
+ f"Critical error in {self.module_name} module main loop: {str(e)}"
77
+ )
78
+ logger.error(f"Traceback: {traceback.format_exc()}")
79
+ await asyncio.sleep(5)
80
+
81
+ async def process(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
82
+ pass
83
+
84
+ def summarize_results(self, scan_results: Dict) -> Dict[str, Any]:
85
+ pass
86
+
87
+ def process_findings(self, scan_results: Dict) -> List[Dict]:
88
+ pass
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: mobsec_modules_library
3
+ Version: 0.0.1
4
+ Summary: Library for writing MobSec Modules using Python
5
+ Author-email: Stormtrooperroman <stormtrooperroman@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Stormtrooperroman/mobsec_modules_library
8
+ Project-URL: Issues, https://github.com/Stormtrooperroman/mobsec_modules_library/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: redis>=4.0.2
15
+ Requires-Dist: fastapi>=0.68.0
16
+ Requires-Dist: uvicorn>=0.15.0
17
+ Dynamic: license-file
18
+
19
+ # MobSec modules library
20
+
21
+ Library to help write modules for [MobSec](https://github.com/Stormtrooperroman/MobSec)
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/mobsec_modules_library/__init__.py
5
+ src/mobsec_modules_library.egg-info/PKG-INFO
6
+ src/mobsec_modules_library.egg-info/SOURCES.txt
7
+ src/mobsec_modules_library.egg-info/dependency_links.txt
8
+ src/mobsec_modules_library.egg-info/requires.txt
9
+ src/mobsec_modules_library.egg-info/top_level.txt
10
+ src/mobsec_modules_library/dynamic/__init__.py
11
+ src/mobsec_modules_library/dynamic/adb_utils.py
12
+ src/mobsec_modules_library/dynamic/base_module_manager.py
13
+ src/mobsec_modules_library/dynamic/base_server.py
14
+ src/mobsec_modules_library/dynamic/base_websocket_manager.py
15
+ src/mobsec_modules_library/static/static_module.py
@@ -0,0 +1,3 @@
1
+ redis>=4.0.2
2
+ fastapi>=0.68.0
3
+ uvicorn>=0.15.0