├── .gitignore ├── LICENSE ├── README.md ├── mobsfpy ├── __init__.py ├── __main__.py ├── cli.py └── mobsf.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | 4 | .idea/ 5 | *.egg-info/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 klmmr 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MobSFpy 2 | 3 | _Python CLI and wrapper for the Mobile Security Framework (MobSF) REST-API_ 4 | 5 | [Mobile Security Framework (MobSF)](https://github.com/MobSF/Mobile-Security-Framework-MobSF) is an automated framework 6 | for security analysis of mobile apps. 7 | It offers also a REST API which allows much flexibility. 8 | The idea of this package is to allow for easier handling of MobSF when it is used on the command line. 9 | It also allows integrating MobSF in your Python project. 10 | With MobSFpy it should be easy to automate the analysis of many apps because you do not have to load them all manually 11 | into MobSF. 12 | 13 | 14 | _WARNING:_ At the moment this package is work in progress. 15 | So feedback is welcome! 16 | 17 | ## Installation 18 | 19 | Currently, this package is not available via PyPI (maybe this will come at a later point of time). 20 | But you can install directly from source on GitHub. 21 | 22 | Clone from GitHub and install using PIP: 23 | 24 | git clone https://github.com/klmmr/mobsfpy.git && cd mobsfpy 25 | pip install . 26 | 27 | If you want to install it for development, you can also install with the `-e` option. 28 | (All changes in code are directly available.) 29 | 30 | pip install -e . 31 | 32 | ## Usage 33 | 34 | `mobsfpy` can be used either as a standalone commandline tool or as a Python package which can be used in your own code. 35 | 36 | ### CLI 37 | 38 | Hint: You will need an REST API key for your MobSF instance. 39 | It is displayed in the logs of MobSF or can be retrieved from `http://:8000/api_docs`. 40 | Best practice is to set the API-Key using `MOBSF_API_KEY` environment variable. 41 | 42 | 43 | 44 | ```bash 45 | # Upload an app to MobSF 46 | mobsf upload example.apk 47 | 48 | # Upload if server is not running on http://127.0.0.1:8000/ and give REST API key explicit 49 | mobsf -k 7e98...2a37 -s http://example.org upload example.apk 50 | 51 | # Scan a file 52 | mobsf scan apk example.apk 53 | 54 | # Retrieve scan results 55 | mobsf report json 56 | # And save to file 57 | mobsf report json -o result.json 58 | 59 | # Getting help 60 | mobsf -h 61 | mobsf upload -h 62 | ``` 63 | 64 | You can get help everytime using the `-h` command. 65 | 66 | ### Python Package 67 | 68 | You can import the package in your code and access the methods of the `mobsf` class. 69 | 70 | ```python 71 | from mobsfpy import MobSF 72 | 73 | # Give API key 74 | mobsf = MobSF('7e98...2a37') 75 | 76 | mobsf.upload('example.apk') 77 | mobsf.scan('apk', 'example.apk', '') 78 | mobsf.report_json('') 79 | ``` 80 | 81 | ## License 82 | 83 | This software is licensed under MIT license. For more information see `LICENSE` file. 84 | 85 | ## Acknowledgements 86 | 87 | * [Ajin Abraham (@ajinabraham)](https://github.com/ajinabraham) and [many others](https://github.com/MobSF/Mobile-Security-Framework-MobSF#honorable-contributors) - For creating MobSF. 88 | * This package is inspired by [this gist](https://gist.github.com/ajinabraham/0f5de3b0c7b7d3665e54740b9f536d81). 89 | -------------------------------------------------------------------------------- /mobsfpy/__init__.py: -------------------------------------------------------------------------------- 1 | from mobsfpy.mobsf import MobSF 2 | -------------------------------------------------------------------------------- /mobsfpy/__main__.py: -------------------------------------------------------------------------------- 1 | from mobsfpy.cli import main 2 | 3 | if __name__ == '__main__': 4 | main() 5 | -------------------------------------------------------------------------------- /mobsfpy/cli.py: -------------------------------------------------------------------------------- 1 | """Commandline interface for MobSF REST API""" 2 | 3 | import argparse 4 | import json 5 | import logging 6 | import os 7 | 8 | from mobsfpy.mobsf import MobSF, DEFAULT_SERVER 9 | 10 | __API_KEY_ENV_VAR = 'MOBSF_API_KEY' 11 | 12 | 13 | def __cli_upload(args): 14 | mobsf = MobSF(args.apikey, args.server) 15 | result = mobsf.upload(args.file) 16 | 17 | print(result) 18 | 19 | 20 | def __cli_scan(args): 21 | mobsf = MobSF(args.apikey, args.server) 22 | result = mobsf.scan(args.scantype, args.filename, args.hash, rescan=args.rescan) 23 | 24 | print(result) 25 | 26 | 27 | def __cli_scans(args): 28 | mobsf = MobSF(args.apikey, args.server) 29 | result = mobsf.scans(args.page, args.pagesize) 30 | 31 | print(result) 32 | 33 | 34 | def __cli_report_json(args): 35 | mobsf = MobSF(args.apikey, args.server) 36 | result = mobsf.report_json(args.hash) 37 | 38 | if args.output: 39 | if args.output is True: 40 | filename = f'report_{args.hash}.json' 41 | else: 42 | filename = args.output 43 | 44 | with open(filename, 'w') as f: 45 | json.dump(result, f, indent=4) 46 | else: 47 | print(json.dumps(result, indent=4)) 48 | 49 | 50 | def __cli_report_pdf(args): 51 | filename = args.output if args.output else f'report_{args.hash}.pdf' 52 | 53 | mobsf = MobSF(args.apikey, args.server) 54 | result = mobsf.report_pdf(args.hash, pdfname=filename) 55 | 56 | print(f'Wrote report to {result}') 57 | 58 | 59 | def __cli_view_source(args): 60 | mobsf = MobSF(args.apikey, args.server) 61 | result = mobsf.scan(args.scantype, args.filename, args.hash) 62 | 63 | print(result) 64 | 65 | 66 | def __cli_delete(args): 67 | mobsf = MobSF(args.apikey, args.server) 68 | result = mobsf.delete_scan(args.hash) 69 | 70 | print(result) 71 | 72 | 73 | def __configure_logger(verbosity): 74 | logger = logging.getLogger() 75 | 76 | handler = logging.StreamHandler() 77 | formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') 78 | handler.setFormatter(formatter) 79 | logger.addHandler(handler) 80 | 81 | logger.setLevel(logging.WARNING) 82 | 83 | if verbosity == 1: 84 | logger.setLevel(logging.INFO) 85 | elif verbosity >= 2: 86 | logger.setLevel(logging.DEBUG) 87 | 88 | 89 | def main(): 90 | parser = argparse.ArgumentParser(prog='mobsf', description='CLI for using the MobSF REST-API') 91 | 92 | parser.add_argument('--server', '-s', default=DEFAULT_SERVER, type=str, 93 | help=f"Server where MobSF is running. If not given, '{DEFAULT_SERVER}' is assumed.") 94 | parser.add_argument('--apikey', '-k', type=str, default=os.environ.get(__API_KEY_ENV_VAR), 95 | help=f"The REST-API-Key for the MobSF instance. Can also be specified as environment variable " 96 | f"using '{__API_KEY_ENV_VAR}'") 97 | parser.add_argument('--verbose', '-v', action='count', default=0, help='Increase verbosity') 98 | 99 | subparsers = parser.add_subparsers(title='Subcommands', 100 | description='This CLI supports a number of different subcommands which have to ' 101 | 'be used.', 102 | help='Available subcommands', 103 | dest='command') 104 | subparsers.required = True 105 | 106 | parser_upload = subparsers.add_parser('upload', help='Upload a file to MobSF') 107 | parser_upload.add_argument('--noscan', action='store_true', help='Execute no scan after upload') 108 | parser_upload.add_argument('file', type=str, help='The file to upload') 109 | parser_upload.set_defaults(func=__cli_upload) 110 | 111 | parser_scan = subparsers.add_parser('scan', help='Scan a uploaded file with MobSF') 112 | parser_scan.add_argument('--rescan', '-r', action='store_true', help='Rescan the app, default is false') 113 | parser_scan.add_argument('scantype', type=str, choices=['apk', 'zip', 'ipa', 'appx'], help='Scan type') 114 | parser_scan.add_argument('filename', type=str, help='The file to scan') 115 | parser_scan.add_argument('hash', type=str, help='Hash of the scan') 116 | parser_scan.set_defaults(func=__cli_scan) 117 | 118 | parser_scans = subparsers.add_parser('scans', help='List recent scans') 119 | parser_scans.add_argument('--page', '-p', type=int, default=1) 120 | parser_scans.add_argument('--pagesize', '-s', type=int, default=100) 121 | parser_scans.set_defaults(func=__cli_scans) 122 | 123 | parser_report = subparsers.add_parser('report', help='Retrieve report of scan') 124 | parser_report.add_argument('hash', type=str, help='Hash of the scan') 125 | 126 | subparsers_report = parser_report.add_subparsers(title='Type', 127 | description='The format which should be used for the report', 128 | help='Type of report', 129 | dest='report_type') 130 | subparsers_report.required = True 131 | 132 | parser_report_json = subparsers_report.add_parser('json') 133 | parser_report_json.add_argument('--output', '-o', type=str, const=True, nargs='?', 134 | help="The file in which the report should be stored. If option is used wihtout " 135 | "specifying a file, 'report_.json' is assumed.") 136 | parser_report_json.set_defaults(func=__cli_report_json) 137 | 138 | parser_report_pdf = subparsers_report.add_parser('pdf') 139 | parser_report_pdf.add_argument('--output', '-o', type=str, 140 | help="The file in which the report should be stored. If not given, 'report.pdf' " 141 | "is assumed.") 142 | parser_report_pdf.set_defaults(func=__cli_report_pdf) 143 | 144 | parser_view_source = subparsers.add_parser('source', help='View source code of a scan') 145 | parser_view_source.add_argument('scantype', type=str, choices=['apk', 'zip', 'ipa', 'appx'], help='Scan type') 146 | parser_view_source.add_argument('filename', type=str, help='The file to scan') 147 | parser_view_source.add_argument('hash', type=str, help='Hash of the scan') 148 | parser_view_source.set_defaults(func=__cli_view_source) 149 | 150 | parser_delete = subparsers.add_parser('delete', help='Delete scan results') 151 | parser_delete.add_argument('hash', type=str, help='Hash of the scan') 152 | parser_delete.set_defaults(func=__cli_delete) 153 | 154 | args = parser.parse_args() 155 | 156 | if not args.apikey: 157 | parser.print_usage() 158 | exit(f"You have to give an API-Key using '--apikey' for MobSF or you have to define it in an environment " 159 | f"variable named '{__API_KEY_ENV_VAR}'.") 160 | 161 | __configure_logger(args.verbose) 162 | 163 | # Call the functions of the subparsers (previously definet by set_defaults()) 164 | args.func(args) 165 | -------------------------------------------------------------------------------- /mobsfpy/mobsf.py: -------------------------------------------------------------------------------- 1 | """MOBSF REST API Python wrapper""" 2 | 3 | import requests 4 | from requests_toolbelt.multipart.encoder import MultipartEncoder 5 | 6 | import logging 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | DEFAULT_SERVER = 'http://127.0.0.1:8000' 11 | 12 | 13 | class MobSF: 14 | """Represents a MobSF instance.""" 15 | 16 | def __init__(self, apikey, server=None): 17 | self.__server = server.rstrip('/') if server else DEFAULT_SERVER 18 | self.__apikey = apikey 19 | 20 | @property 21 | def server(self): 22 | return self.__server 23 | 24 | @property 25 | def apikey(self): 26 | return self.__apikey 27 | 28 | def upload(self, file): 29 | """Upload an app.""" 30 | logger.debug(f"Uploading {file} to {self.__server}") 31 | 32 | multipart_data = MultipartEncoder(fields={'file': (file, open(file, 'rb'), 'application/octet-stream')}) 33 | headers = {'Content-Type': multipart_data.content_type, 'Authorization': self.__apikey} 34 | 35 | r = requests.post(f'{self.__server}/api/v1/upload', data=multipart_data, headers=headers) 36 | 37 | return r.json() 38 | 39 | def scan(self, scantype, filename, scanhash, rescan=False): 40 | """Scan already uploaded file. 41 | 42 | If the file was not uploaded before you will have to do so first. 43 | """ 44 | logger.debug(f"Requesting {self.__server} to scan {scanhash} ({filename}, {scantype})") 45 | 46 | post_dict = {'scan_type': scantype, 47 | 'file_name': filename, 48 | 'hash': scanhash, 49 | 're_scan': rescan} 50 | 51 | headers = {'Authorization': self.__apikey} 52 | 53 | r = requests.post(f'{self.__server}/api/v1/scan', data=post_dict, headers=headers) 54 | 55 | return r.json() 56 | 57 | def scans(self, page=1, page_size=100): 58 | """Show recent scans.""" 59 | logger.debug(f'Requesting recent scans from {self.__server}') 60 | 61 | payload = {'page': page, 62 | 'page_size': page_size} 63 | headers = {'Authorization': self.__apikey} 64 | 65 | r = requests.get(f'{self.__server}/api/v1/scans', params=payload, headers=headers) 66 | 67 | return r.json() 68 | 69 | def report_pdf(self, scanhash, pdfname=None): 70 | """Retrieve and store a scan report as PDF.""" 71 | pdfname = pdfname if pdfname else 'report.pdf' 72 | 73 | logger.debug(f'Requesting PDF report for scan {scanhash}') 74 | 75 | headers = {'Authorization': self.__apikey} 76 | data = {'hash': scanhash} 77 | 78 | r = requests.post(f'{self.__server}/api/v1/download_pdf', data=data, headers=headers, stream=True) 79 | 80 | logger.debug(f'Writing PDF report to {pdfname}') 81 | with open(pdfname, 'wb') as pdf: 82 | for chunk in r.iter_content(chunk_size=1024): 83 | if chunk: 84 | pdf.write(chunk) 85 | 86 | logger.info(f'Report saved as {pdfname}') 87 | 88 | return pdfname 89 | 90 | def report_json(self, scanhash): 91 | """Retrieve JSON report of a scan.""" 92 | logger.debug(f'Requesting JSON report for scan {scanhash}') 93 | 94 | headers = {'Authorization': self.__apikey} 95 | data = {'hash': scanhash} 96 | 97 | r = requests.post(f'{self.__server}/api/v1/report_json', data=data, headers=headers) 98 | 99 | return r.json() 100 | 101 | def view_source(self, scantype, filename, scanhash): 102 | """Retrieve source files of a scan.""" 103 | logger.debug(f'Requesting source files for {scanhash} ({filename}, {scantype})') 104 | 105 | headers = {'Authorization': self.__apikey} 106 | data = {'type': scantype, 107 | 'hash': scanhash, 108 | 'file': filename} 109 | 110 | r = requests.post(f'{self.__server}/api/v1/view_source', data=data, headers=headers) 111 | 112 | return r.json() 113 | 114 | def delete_scan(self, scanhash): 115 | """Delete a scan result.""" 116 | logger.debug(f'Requesting {self.__server} to delete scan {scanhash}') 117 | 118 | headers = {'Authorization': self.__apikey} 119 | data = {'hash': scanhash} 120 | 121 | r = requests.post(f'{self.__server}/api/v1/delete_scan', data=data, headers=headers) 122 | 123 | return r.json() 124 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open('README.md', 'r') as f: 4 | long_description = f.read() 5 | 6 | setuptools.setup( 7 | name='mobsfpy', 8 | version='0.0.1-dev', 9 | author='klmmr', 10 | author_email='klmmr@klmmr.de', 11 | description='Python CLI and wrapper for the Mobile Security Framework (MobSF) REST-API ', 12 | license='MIT', 13 | long_description=long_description, 14 | long_description_content_type='text/markdown', 15 | url='https://github.com/klmmr/mobsfpy', 16 | packages=setuptools.find_packages(), 17 | install_requires=[ 18 | 'requests', 19 | 'requests-toolbelt', 20 | ], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'mobsf = mobsfpy.cli:main' 24 | ] 25 | }, 26 | classifiers=[ 27 | 'Development Status :: 2 - Pre-Alpha', 28 | 'Environment :: Console', 29 | 'Programming Language :: Python', 30 | 'Programming Language :: Python :: 3', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Natural Language :: English', 33 | 'Operating System :: OS Independent', 34 | 'Topic :: Security', 35 | 'Topic :: Utilities', 36 | ], 37 | python_requires='>=3.6', 38 | keywords='mobsf api cli android ios mobile security analysis', 39 | ) 40 | --------------------------------------------------------------------------------