mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-11 22:50:09 +00:00
Dynamic Analysis with Genymotion. Supports Android 4.1 -9.0 (#1067)
Dynamic Analysis with Genymotion. Supports Android 4.1 -9.0 Static Analyzer Threaded baksmali Improved Recent Scan Browser PDF Generation Dynamic Analyzer Restructuring Dynamic Analyzer Code QA and Modularization Automatic MobSFy with Xposed and Frida Full pass on all versions of android with basic features Streaming logcat Live API Monitor Better SQlite DB View In line frida scripting + loading from frida code share basic frida env, ssl unpin, root check, debugger detection bypass. Custom Frida Scripts and integration Frida Log Viewer Frida scripts loader Frida custom user script loader
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
sonar.sources=.
|
||||
sonar.exclusions=static/**/*,templates/**/*,DynamicAnalyzer/tools/adb/**/*
|
||||
sonar.exclusions=static/**/*,templates/**/*
|
||||
sonar.sourceEncoding=UTF-8
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ matrix:
|
||||
sudo: false
|
||||
before_install:
|
||||
- export TRAVIS_COMMIT_MSG="$(git log --format=%B --no-merges -n 1)"
|
||||
- sudo apt-get install -y libpng16-16 libssl-dev xfonts-75dpi xfonts-base libffi-dev
|
||||
libxml2-dev libxslt1-dev openssl
|
||||
- sudo apt-get install -y libpng16-16 xfonts-75dpi xfonts-base libffi-dev
|
||||
libxml2-dev libxslt1-dev
|
||||
- wget https://downloads.wkhtmltopdf.org/0.12/0.12.5/wkhtmltox_0.12.5-1.xenial_amd64.deb
|
||||
- sudo dpkg -i wkhtmltox_0.12.5-1.xenial_amd64.deb
|
||||
- name: Python 3.7 on OSX, Xcode 11
|
||||
|
||||
+18
-12
@@ -8,18 +8,17 @@ LABEL \
|
||||
maintainer="Ajin Abraham <[email protected]>" \
|
||||
contributor_1="OscarAkaElvis <[email protected]>" \
|
||||
contributor_2="Vincent Nadal <[email protected]>" \
|
||||
description="Mobile Security Framework is an intelligent, all-in-one open source mobile application (Android/iOS/Windows) automated pen-testing framework capable of performing static, dynamic analysis and web API testing"
|
||||
description="Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis."
|
||||
|
||||
#Environment vars
|
||||
ENV DEBIAN_FRONTEND="noninteractive" \
|
||||
ANALYZER_IDENTIFIER="" \
|
||||
JDK_FILE="openjdk-12_linux-x64_bin.tar.gz" \
|
||||
WKH_FILE="wkhtmltox-0.12.5-dev-163e124_linux-generic-amd64.tar.xz"
|
||||
WKH_FILE="wkhtmltox_0.12.1.4-1.bionic_amd64.deb"
|
||||
|
||||
ENV JDK_URL="https://download.java.net/java/GA/jdk12/GPL/${JDK_FILE}" \
|
||||
WKH_URL="http://www.ajvg.com/downloads/${WKH_FILE}"
|
||||
WKH_URL="https://builds.wkhtmltopdf.org/0.12.1.4/${WKH_FILE}"
|
||||
|
||||
#Update the repository sources list
|
||||
#Install Required Libs
|
||||
#see https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
|
||||
RUN apt update -y && apt install -y \
|
||||
build-essential \
|
||||
@@ -37,19 +36,23 @@ RUN apt update -y && apt install -y \
|
||||
libxext6 \
|
||||
fontconfig \
|
||||
xfonts-75dpi \
|
||||
xfonts-base \
|
||||
python3.6 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
wget
|
||||
wget \
|
||||
android-tools-adb
|
||||
|
||||
#set locales
|
||||
RUN locale-gen en_US.UTF-8
|
||||
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
|
||||
|
||||
#Install Wkhtmltopdf
|
||||
RUN wget --quiet -O /tmp/wkhtmltox.tar.xz "${WKH_URL}" && \
|
||||
tar xJf /tmp/wkhtmltox.tar.xz -C /usr/bin/ --strip-component=2 wkhtmltox/bin/wkhtmltopdf && \
|
||||
rm -f /tmp/wkhtmltox.tar.xz
|
||||
RUN wget --quiet -O /tmp/${WKH_FILE} "${WKH_URL}" && \
|
||||
dpkg -i /tmp/${WKH_FILE} && \
|
||||
apt-get install -f -y --no-install-recommends && \
|
||||
ln -s /usr/local/bin/wkhtmltopdf /usr/bin && \
|
||||
rm -f /tmp/${WKH_FILE}
|
||||
|
||||
#Install OpenJDK12
|
||||
RUN wget --quiet "${JDK_URL}" && \
|
||||
@@ -62,8 +65,9 @@ ENV PATH="$JAVA_HOME/bin:$PATH"
|
||||
COPY . /root/Mobile-Security-Framework-MobSF
|
||||
WORKDIR /root/Mobile-Security-Framework-MobSF
|
||||
|
||||
#Enable Use Home Directory
|
||||
RUN sed -i 's/USE_HOME = False/USE_HOME = True/g' MobSF/settings.py
|
||||
#Enable Use Home Directory and set adb path
|
||||
RUN sed -i 's/USE_HOME = False/USE_HOME = True/g' MobSF/settings.py && \
|
||||
sed -i "s#ADB_BINARY = ''#ADB_BINARY = '/usr/bin/adb'#" MobSF/settings.py
|
||||
|
||||
#Kali fix to support 32 bit execution
|
||||
RUN ./scripts/kali_fix.sh
|
||||
@@ -103,10 +107,12 @@ RUN \
|
||||
|
||||
#Expose MobSF Port
|
||||
EXPOSE 8000
|
||||
# MobSF Proxy
|
||||
EXPOSE 1337
|
||||
|
||||
RUN python3 manage.py makemigrations && \
|
||||
python3 manage.py makemigrations StaticAnalyzer && \
|
||||
python3 manage.py migrate
|
||||
|
||||
#Run MobSF
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:8000", "MobSF.wsgi:application", "--workers=1", "--threads=4", "--timeout=1800"]
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:8000", "MobSF.wsgi:application", "--workers=1", "--threads=10", "--timeout=1800"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
Pkg.UserSrc=false
|
||||
Pkg.Revision=26.0.0
|
||||
#Pkg.Revision=26.0.0 rc2
|
||||
Binary file not shown.
@@ -1,205 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1 +0,0 @@
|
||||
c69b78719398903e6916f572bd7383afbbb2cdb9
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
version = sys.version_info[:2]
|
||||
if version != (2, 7):
|
||||
sys.stderr.write('Systrace does not support Python %d.%d. '
|
||||
'Please use Python 2.7.\n' % version)
|
||||
sys.exit(1)
|
||||
|
||||
systrace_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), 'catapult', 'systrace'))
|
||||
sys.path.insert(0, systrace_dir)
|
||||
|
||||
def RemoveAllStalePycFiles(base_dir):
|
||||
"""Scan directories for old .pyc files without a .py file and delete them."""
|
||||
for dirname, _, filenames in os.walk(base_dir):
|
||||
if '.git' in dirname:
|
||||
continue
|
||||
for filename in filenames:
|
||||
root, ext = os.path.splitext(filename)
|
||||
if ext != '.pyc':
|
||||
continue
|
||||
|
||||
pyc_path = os.path.join(dirname, filename)
|
||||
py_path = os.path.join(dirname, root + '.py')
|
||||
|
||||
try:
|
||||
if not os.path.exists(py_path):
|
||||
os.remove(pyc_path)
|
||||
except OSError:
|
||||
# Wrap OS calls in try/except in case another process touched this file.
|
||||
pass
|
||||
|
||||
try:
|
||||
os.removedirs(dirname)
|
||||
except OSError:
|
||||
# Wrap OS calls in try/except in case another process touched this dir.
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
RemoveAllStalePycFiles(os.path.dirname(__file__))
|
||||
from systrace import run_systrace
|
||||
sys.exit(run_systrace.main())
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
Pkg.UserSrc=false
|
||||
Pkg.Revision=26.0.0
|
||||
#Pkg.Revision=26.0.0 rc2
|
||||
Binary file not shown.
@@ -1,205 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1 +0,0 @@
|
||||
c69b78719398903e6916f572bd7383afbbb2cdb9
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
version = sys.version_info[:2]
|
||||
if version != (2, 7):
|
||||
sys.stderr.write('Systrace does not support Python %d.%d. '
|
||||
'Please use Python 2.7.\n' % version)
|
||||
sys.exit(1)
|
||||
|
||||
systrace_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), 'catapult', 'systrace'))
|
||||
sys.path.insert(0, systrace_dir)
|
||||
|
||||
def RemoveAllStalePycFiles(base_dir):
|
||||
"""Scan directories for old .pyc files without a .py file and delete them."""
|
||||
for dirname, _, filenames in os.walk(base_dir):
|
||||
if '.git' in dirname:
|
||||
continue
|
||||
for filename in filenames:
|
||||
root, ext = os.path.splitext(filename)
|
||||
if ext != '.pyc':
|
||||
continue
|
||||
|
||||
pyc_path = os.path.join(dirname, filename)
|
||||
py_path = os.path.join(dirname, root + '.py')
|
||||
|
||||
try:
|
||||
if not os.path.exists(py_path):
|
||||
os.remove(pyc_path)
|
||||
except OSError:
|
||||
# Wrap OS calls in try/except in case another process touched this file.
|
||||
pass
|
||||
|
||||
try:
|
||||
os.removedirs(dirname)
|
||||
except OSError:
|
||||
# Wrap OS calls in try/except in case another process touched this dir.
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
RemoveAllStalePycFiles(os.path.dirname(__file__))
|
||||
from systrace import run_systrace
|
||||
sys.exit(run_systrace.main())
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
Pkg.UserSrc=false
|
||||
Pkg.Revision=26.0.0
|
||||
#Pkg.Revision=26.0.0 rc2
|
||||
Binary file not shown.
@@ -1,205 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1 +0,0 @@
|
||||
c69b78719398903e6916f572bd7383afbbb2cdb9
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
version = sys.version_info[:2]
|
||||
if version != (2, 7):
|
||||
sys.stderr.write('Systrace does not support Python %d.%d. '
|
||||
'Please use Python 2.7.\n' % version)
|
||||
sys.exit(1)
|
||||
|
||||
systrace_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), 'catapult', 'systrace'))
|
||||
sys.path.insert(0, systrace_dir)
|
||||
|
||||
def RemoveAllStalePycFiles(base_dir):
|
||||
"""Scan directories for old .pyc files without a .py file and delete them."""
|
||||
for dirname, _, filenames in os.walk(base_dir):
|
||||
if '.git' in dirname:
|
||||
continue
|
||||
for filename in filenames:
|
||||
root, ext = os.path.splitext(filename)
|
||||
if ext != '.pyc':
|
||||
continue
|
||||
|
||||
pyc_path = os.path.join(dirname, filename)
|
||||
py_path = os.path.join(dirname, root + '.py')
|
||||
|
||||
try:
|
||||
if not os.path.exists(py_path):
|
||||
os.remove(pyc_path)
|
||||
except OSError:
|
||||
# Wrap OS calls in try/except in case another process touched this file.
|
||||
pass
|
||||
|
||||
try:
|
||||
os.removedirs(dirname)
|
||||
except OSError:
|
||||
# Wrap OS calls in try/except in case another process touched this dir.
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
RemoveAllStalePycFiles(os.path.dirname(__file__))
|
||||
from systrace import run_systrace
|
||||
sys.exit(run_systrace.main())
|
||||
@@ -0,0 +1,126 @@
|
||||
// https://github.com/iddoeldor/frida-snippets#trace-class
|
||||
/**
|
||||
*
|
||||
* @param input.
|
||||
* If an object is passed it will print as json
|
||||
* @param kwargs options map {
|
||||
* -l level: string; log/warn/error
|
||||
* -i indent: boolean; print JSON prettify
|
||||
* -c color: @see ColorMap
|
||||
* }
|
||||
var Color = {
|
||||
RESET: "\x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;11", Green: "2;01", Purple: "5;01", Red: "1;01", Yellow: "3;01",
|
||||
Light: {
|
||||
Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", Green: "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11"
|
||||
}
|
||||
};
|
||||
|
||||
var LOG = function (input, kwargs) {
|
||||
kwargs = kwargs || {};
|
||||
var logLevel = kwargs['l'] || 'log', colorPrefix = '\x1b[3', colorSuffix = 'm';
|
||||
if (typeof input === 'object')
|
||||
input = JSON.stringify(input, null, kwargs['i'] ? 2 : null);
|
||||
if (kwargs['c'])
|
||||
input = colorPrefix + kwargs['c'] + colorSuffix + input + Color.RESET;
|
||||
console[logLevel](input);
|
||||
};
|
||||
|
||||
var printBacktrace = function () {
|
||||
Java.perform(function() {
|
||||
var android_util_Log = Java.use('android.util.Log'), java_lang_Exception = Java.use('java.lang.Exception');
|
||||
// getting stacktrace by throwing an exception
|
||||
LOG(android_util_Log.getStackTraceString(java_lang_Exception.$new()), { c: Color.Gray });
|
||||
});
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
function traceClass(targetClass) {
|
||||
var hook;
|
||||
try {
|
||||
hook = Java.use(targetClass);
|
||||
send('[AUXILIARY] [Class Trace] Tracing all methods implementations of : '+ targetClass)
|
||||
} catch (e) {
|
||||
send('[AUXILIARY] [Class Trace] Error - ' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
var methods = hook.class.getDeclaredMethods();
|
||||
hook.$dispose();
|
||||
|
||||
var parsedMethods = [];
|
||||
methods.forEach(function (method) {
|
||||
var methodStr = method.toString();
|
||||
var methodReplace = methodStr.replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1];
|
||||
parsedMethods.push(methodReplace);
|
||||
});
|
||||
|
||||
uniqBy(parsedMethods, JSON.stringify).forEach(function (targetMethod) {
|
||||
traceMethod(targetClass + '.' + targetMethod);
|
||||
});
|
||||
}
|
||||
|
||||
function traceMethod(targetClassMethod) {
|
||||
var delim = targetClassMethod.lastIndexOf('.');
|
||||
if (delim === -1)
|
||||
return;
|
||||
|
||||
var targetClass = targetClassMethod.slice(0, delim);
|
||||
var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length);
|
||||
|
||||
var hook = Java.use(targetClass);
|
||||
var overloadCount = hook[targetMethod].overloads.length;
|
||||
|
||||
// LOG({ tracing: targetClassMethod, overloaded: overloadCount }, { c: Color.Green });
|
||||
for (var i = 0; i < overloadCount; i++) {
|
||||
hook[targetMethod].overloads[i].implementation = function () {
|
||||
var log = { 'Class Trace': targetClassMethod, args: [] };
|
||||
|
||||
for (var j = 0; j < arguments.length; j++) {
|
||||
var arg = arguments[j];
|
||||
// quick&dirty fix for java.io.StringWriter char[].toString() impl because frida prints [object Object]
|
||||
if (j === 0 && arguments[j]) {
|
||||
if (arguments[j].toString() === '[object Object]') {
|
||||
var s = [];
|
||||
for (var k = 0, l = arguments[j].length; k < l; k++) {
|
||||
s.push(arguments[j][k]);
|
||||
}
|
||||
arg = s.join('');
|
||||
}
|
||||
}
|
||||
log.args.push({ i: j, o: arg, s: arg ? arg.toString(): 'null'});
|
||||
}
|
||||
|
||||
var retval;
|
||||
try {
|
||||
retval = this[targetMethod].apply(this, arguments); // might crash (Frida bug?)
|
||||
log.returns = { val: retval, str: retval ? retval.toString() : null };
|
||||
} catch (e) {
|
||||
send('[AUXILIARY] [Class Trace] Error - ' + e)
|
||||
}
|
||||
//LOG(log, { c: Color.Blue });
|
||||
send(log)
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates from array
|
||||
function uniqBy(array, key) {
|
||||
var seen = {};
|
||||
return array.filter(function (item) {
|
||||
var k = key(item);
|
||||
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Java.perform(function () { // avoid java.lang.ClassNotFoundException
|
||||
{{CLASSES}}.forEach(traceClass);
|
||||
|
||||
/* Java.use('java.net.Socket').isConnected.overload().implementation = function () {
|
||||
LOG('Socket.isConnected.overload', { c: Color.Light.Cyan });
|
||||
printBacktrace();
|
||||
return true;
|
||||
} */
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* raptor_frida_android_enum.js - Java class/method enumerator
|
||||
* Copyright (c) 2017 Marco Ivaldi <[email protected]>
|
||||
*
|
||||
*/
|
||||
Java.perform(function() {
|
||||
var classes = Java.enumerateLoadedClassesSync();
|
||||
send('[AUXILIARY] Loaded Classes');
|
||||
classes.forEach(function(aClass) {
|
||||
try{
|
||||
var className = aClass.match(/[L](.*);/)[1].replace(/\//g, ".");
|
||||
send('[AUXILIARY] ' + className);
|
||||
}
|
||||
catch(err){}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* raptor_frida_android_enum.js - Java class/method enumerator
|
||||
* Copyright (c) 2017 Marco Ivaldi <[email protected]>
|
||||
*
|
||||
*/
|
||||
Java.perform(function() {
|
||||
var hook;
|
||||
var targetClass = '{{CLASS}}';
|
||||
try{
|
||||
send('[AUXILIARY] Getting Methods and Implementations of Class: ' + targetClass)
|
||||
hook = Java.use(targetClass);
|
||||
} catch (err){
|
||||
send('[AUXILIARY] Hooking ' + targetClass + ' [\"Error\"] => ' + err);
|
||||
return;
|
||||
}
|
||||
var methods = hook.class.getDeclaredMethods();
|
||||
hook.$dispose;
|
||||
methods.forEach(function(method) {
|
||||
send('[AUXILIARY] ' + method)
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* raptor_frida_android_enum.js - Java class/method enumerator
|
||||
* Copyright (c) 2017 Marco Ivaldi <[email protected]>
|
||||
*
|
||||
*/
|
||||
function enumAllClasses()
|
||||
{
|
||||
var allClasses = [];
|
||||
var classes = Java.enumerateLoadedClassesSync();
|
||||
classes.forEach(function(aClass) {
|
||||
try {
|
||||
var className = aClass.match(/[L](.*);/)[1].replace(/\//g, ".");
|
||||
}
|
||||
catch(err) {}
|
||||
allClasses.push(className);
|
||||
});
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
function findClasses(pattern)
|
||||
{
|
||||
var allClasses = enumAllClasses();
|
||||
var foundClasses = [];
|
||||
allClasses.forEach(function(aClass) {
|
||||
try {
|
||||
if (aClass.match(pattern)) {
|
||||
foundClasses.push(aClass);
|
||||
}
|
||||
}
|
||||
catch(err) {}
|
||||
});
|
||||
return foundClasses;
|
||||
}
|
||||
|
||||
|
||||
Java.perform(function() {
|
||||
|
||||
|
||||
var matches;
|
||||
try{
|
||||
var pattern = /{{PATTERN}}/i;
|
||||
send('[AUXILIARY] Class search for pattern: ' + pattern)
|
||||
matches = findClasses(pattern);
|
||||
}catch (err){
|
||||
send('[AUXILIARY] Class pattern match [\"Error\"] => ' + err);
|
||||
return;
|
||||
}
|
||||
if (matches.length>0)
|
||||
send('[AUXILIARY] Pattern matches found')
|
||||
else
|
||||
send('[AUXILIARY] No matches found')
|
||||
matches.forEach(function(clz) {
|
||||
send('[AUXILIARY] ' + clz)
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
Java.perform(function() {
|
||||
['java.lang.StringBuilder', 'java.lang.StringBuffer'].forEach(function(clazz, i) {
|
||||
var func = 'toString';
|
||||
Java.use(clazz)[func].implementation = function() {
|
||||
var ret = this[func]();
|
||||
send('[AUXILIARY] [String Catch] [' + i + '] ' + ret);
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
//https://github.com/iddoeldor/frida-snippets#reveal-native-methods
|
||||
//String comparison
|
||||
Java.perform(function () {
|
||||
send('[AUXILIARY] [String Compare] capturing all string comparisons')
|
||||
var str = Java.use('java.lang.String'), objectClass = 'java.lang.Object';
|
||||
str.equals.overload(objectClass).implementation = function (obj) {
|
||||
var response = str.equals.overload(objectClass).call(this, obj);
|
||||
if (obj) {
|
||||
if (obj.toString().length > 5) {
|
||||
send('[AUXILIARY] [String Compare] ' + str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,581 @@
|
||||
// MobSF Android API Monitor
|
||||
// Inspired from: https://github.com/realgam3/ReversingAutomation/blob/master/Frida/Android-DynamicHooks/DynamicHooks.js
|
||||
|
||||
var apis = [{
|
||||
class: 'android.os.Process',
|
||||
method: 'start',
|
||||
name: 'Process'
|
||||
}, {
|
||||
class: 'android.app.ActivityManager',
|
||||
method: 'killBackgroundProcesses',
|
||||
name: 'Process'
|
||||
}, {
|
||||
class: 'android.os.Process',
|
||||
method: 'killProcess',
|
||||
name: 'Process'
|
||||
}, {
|
||||
class: 'java.lang.Runtime',
|
||||
method: 'exec',
|
||||
name: 'Command'
|
||||
}, {
|
||||
class: 'java.lang.ProcessBuilder',
|
||||
method: 'start',
|
||||
name: 'Command'
|
||||
}, {
|
||||
class: 'java.lang.Runtime',
|
||||
method: 'loadLibrary',
|
||||
name: 'Java Native Interface'
|
||||
}, {
|
||||
class: 'java.lang.Runtime',
|
||||
method: 'load',
|
||||
name: 'Java Native Interface'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'loadUrl',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'loadData',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'loadDataWithBaseURL',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'addJavascriptInterface',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'evaluateJavascript',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'postUrl',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'postWebMessage',
|
||||
name: 'WebView',
|
||||
target: 6
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'savePassword',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'setHttpAuthUsernamePassword',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'getHttpAuthUsernamePassword',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'android.webkit.WebView',
|
||||
method: 'setWebContentsDebuggingEnabled',
|
||||
name: 'WebView'
|
||||
}, {
|
||||
class: 'libcore.io.IoBridge',
|
||||
method: 'open',
|
||||
name: 'File IO'
|
||||
},/* {
|
||||
// so much calls
|
||||
class: 'java.io.FileOutputStream',
|
||||
method: 'write',
|
||||
name: 'File IO'
|
||||
}, {
|
||||
class: 'java.io.FileInputStream',
|
||||
method: 'read',
|
||||
name: 'File IO'
|
||||
}, */ {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'openFileInput',
|
||||
name: 'File IO'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'openFileOutput',
|
||||
name: 'File IO'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'deleteFile',
|
||||
name: 'File IO'
|
||||
}, /* {
|
||||
// crashes app on android 7
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'getString',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'contains',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'getInt',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'getFloat',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'getLong',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'getBoolean',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl',
|
||||
method: 'getStringSet',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'putString',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'putStringSet',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'putInt',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'putFloat',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'putBoolean',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'putLong',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, {
|
||||
class: 'android.app.SharedPreferencesImpl$EditorImpl',
|
||||
method: 'remove',
|
||||
name: 'File IO - Shared Preferences'
|
||||
}, */ {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'openOrCreateDatabase',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'databaseList',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'deleteDatabase',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'execSQL',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'deleteDatabase',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'getPath',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'insert',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'insertOrThrow',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'insertWithOnConflict',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'openDatabase',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'openOrCreateDatabase',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'query',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'queryWithFactory',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'rawQuery',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'rawQueryWithFactory',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'update',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'updateWithOnConflict',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'compileStatement',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.database.sqlite.SQLiteDatabase',
|
||||
method: 'create',
|
||||
name: 'Database'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'sendBroadcast',
|
||||
name: 'IPC'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'sendStickyBroadcast',
|
||||
name: 'IPC'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'startActivity',
|
||||
name: 'IPC'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'startService',
|
||||
name: 'IPC'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'stopService',
|
||||
name: 'IPC'
|
||||
}, {
|
||||
class: 'android.content.ContextWrapper',
|
||||
method: 'registerReceiver',
|
||||
name: 'IPC'
|
||||
}, {
|
||||
class: 'android.app.ContextImpl',
|
||||
method: 'registerReceiver',
|
||||
name: 'Binder'
|
||||
}, {
|
||||
class: 'android.app.ActivityThread',
|
||||
method: 'handleReceiver',
|
||||
name: 'Binder'
|
||||
}, {
|
||||
class: 'android.app.Activity',
|
||||
method: 'startActivity',
|
||||
name: 'Binder'
|
||||
}, {
|
||||
class: 'javax.crypto.spec.SecretKeySpec',
|
||||
method: '$init',
|
||||
name: 'Crypto'
|
||||
}, {
|
||||
class: 'javax.crypto.Cipher',
|
||||
method: 'doFinal',
|
||||
name: 'Crypto'
|
||||
}, {
|
||||
class: 'java.security.MessageDigest',
|
||||
method: 'digest',
|
||||
name: 'Crypto - Hash'
|
||||
}, {
|
||||
class: 'java.security.MessageDigest',
|
||||
method: 'update',
|
||||
name: 'Crypto - Hash'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getDeviceId',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getSubscriberId',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getLine1Number',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getNetworkOperator',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getNetworkOperatorName',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getSimOperatorName',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.net.wifi.WifiInfo',
|
||||
method: 'getMacAddress',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.net.wifi.WifiInfo',
|
||||
method: 'getBSSID',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.net.wifi.WifiInfo',
|
||||
method: 'getIpAddress',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.net.wifi.WifiInfo',
|
||||
method: 'getNetworkId',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getSimCountryIso',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getSimSerialNumber',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getNetworkCountryIso',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'getDeviceSoftwareVersion',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.os.Debug',
|
||||
method: 'isDebuggerConnected',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.content.pm.PackageManager',
|
||||
method: 'getInstallerPackageName',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.content.pm.PackageManager',
|
||||
method: 'getInstalledApplications',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'android.content.pm.PackageManager',
|
||||
method: 'getInstalledModules',
|
||||
name: 'Device Info',
|
||||
target: 10,
|
||||
}, {
|
||||
class: 'android.content.pm.PackageManager',
|
||||
method: 'getInstalledPackages',
|
||||
name: 'Device Info'
|
||||
}, {
|
||||
class: 'java.net.URL',
|
||||
method: 'openConnection',
|
||||
name: 'Network'
|
||||
}, {
|
||||
class: 'org.apache.http.impl.client.AbstractHttpClient',
|
||||
method: 'execute',
|
||||
name: 'Network'
|
||||
}, {
|
||||
class: 'com.android.okhttp.internal.huc.HttpURLConnectionImpl',
|
||||
method: 'getInputStream',
|
||||
name: 'Network'
|
||||
}, {
|
||||
class: 'com.android.okhttp.internal.http.HttpURLConnectionImpl',
|
||||
method: 'getInputStream',
|
||||
name: 'Network'
|
||||
}, {
|
||||
class: 'dalvik.system.BaseDexClassLoader',
|
||||
method: 'findResource',
|
||||
name: 'Dex Class Loader'
|
||||
}, {
|
||||
class: 'dalvik.system.BaseDexClassLoader',
|
||||
method: 'findResources',
|
||||
name: 'Dex Class Loader'
|
||||
}, {
|
||||
class: 'dalvik.system.BaseDexClassLoader',
|
||||
method: 'findLibrary',
|
||||
name: 'Dex Class Loader'
|
||||
}, {
|
||||
class: 'dalvik.system.DexFile',
|
||||
method: 'loadDex',
|
||||
name: 'Dex Class Loader'
|
||||
}, {
|
||||
class: 'dalvik.system.DexFile',
|
||||
method: 'loadClass',
|
||||
name: 'Dex Class Loader'
|
||||
}, {
|
||||
class: 'dalvik.system.DexClassLoader',
|
||||
method: '$init',
|
||||
name: 'Dex Class Loader'
|
||||
}, {
|
||||
class: 'android.util.Base64',
|
||||
method: 'decode',
|
||||
name: 'Base64'
|
||||
}, {
|
||||
class: 'android.util.Base64',
|
||||
method: 'encode',
|
||||
name: 'Base64'
|
||||
}, {
|
||||
class: 'android.util.Base64',
|
||||
method: 'encodeToString',
|
||||
name: 'Base64'
|
||||
}, {
|
||||
class: 'android.app.ApplicationPackageManager',
|
||||
method: 'setComponentEnabledSetting',
|
||||
name: 'System Manager'
|
||||
}, {
|
||||
class: 'android.app.NotificationManager',
|
||||
method: 'notify',
|
||||
name: 'System Manager'
|
||||
}, {
|
||||
class: 'android.telephony.TelephonyManager',
|
||||
method: 'listen',
|
||||
name: 'System Manager'
|
||||
}, {
|
||||
class: 'android.content.BroadcastReceiver',
|
||||
method: 'abortBroadcast',
|
||||
name: 'System Manager'
|
||||
}, {
|
||||
class: 'android.telephony.SmsManager',
|
||||
method: 'sendTextMessage',
|
||||
name: 'SMS'
|
||||
}, {
|
||||
class: 'android.telephony.SmsManager',
|
||||
method: 'sendMultipartTextMessage',
|
||||
name: 'SMS'
|
||||
}, {
|
||||
class: 'android.content.ContentResolver',
|
||||
method: 'query',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.content.ContentResolver',
|
||||
method: 'registerContentObserver',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.content.ContentResolver',
|
||||
method: 'insert',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.content.ContentResolver',
|
||||
method: 'delete',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.accounts.AccountManager',
|
||||
method: 'getAccountsByType',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.accounts.AccountManager',
|
||||
method: 'getAccounts',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.location.Location',
|
||||
method: 'getLatitude',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.location.Location',
|
||||
method: 'getLongitude',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.media.AudioRecord',
|
||||
method: 'startRecording',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.media.MediaRecorder',
|
||||
method: 'start',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.os.SystemProperties',
|
||||
method: 'get',
|
||||
name: 'Device Data'
|
||||
}, {
|
||||
class: 'android.app.ApplicationPackageManager',
|
||||
method: 'getInstalledPackages',
|
||||
name: 'Device Data'
|
||||
}];
|
||||
|
||||
// Get All Method Implementations
|
||||
function get_implementations(toHook) {
|
||||
var imp_args = []
|
||||
toHook.overloads.forEach(function (impl, _) {
|
||||
if (impl.hasOwnProperty('argumentTypes')) {
|
||||
var args = [];
|
||||
var argTypes = impl.argumentTypes
|
||||
argTypes.forEach(function (arg_type, __) {
|
||||
args.push(arg_type.className)
|
||||
});
|
||||
imp_args.push(args);
|
||||
}
|
||||
});
|
||||
return imp_args;
|
||||
}
|
||||
|
||||
// Dynamic Hooks
|
||||
function hook(api, callback) {
|
||||
var Exception = Java.use('java.lang.Exception');
|
||||
var toHook;
|
||||
try {
|
||||
var clazz = api.class;
|
||||
var method = api.method;
|
||||
var name = api.name;
|
||||
try {
|
||||
if (api.target && parseInt(Java.androidVersion, 10) < api.target) {
|
||||
// send('[API Monitor] Not Hooking unavailable class/method - ' + clazz + '.' + method)
|
||||
return
|
||||
}
|
||||
// Check if class and method is available
|
||||
toHook = Java.use(clazz)[method];
|
||||
if (!toHook) {
|
||||
send('[API Monitor] Cannot find ' + clazz + '.' + method);
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
send('[API Monitor] Cannot find ' + clazz + '.' + method);
|
||||
return
|
||||
}
|
||||
var arglist = get_implementations(toHook)
|
||||
arglist.forEach(function (args, _) {
|
||||
toHook.overload.apply(null, args).implementation = function () {
|
||||
var argz = [].slice.call(arguments);
|
||||
// Call original function
|
||||
var result = this[method].apply(this, argz);
|
||||
if (callback) {
|
||||
var calledFrom = Exception.$new().getStackTrace().toString().split(',')[1];
|
||||
var message = {
|
||||
name: name,
|
||||
class: clazz,
|
||||
method: method,
|
||||
arguments: argz,
|
||||
result: result,
|
||||
calledFrom: calledFrom
|
||||
};
|
||||
result = callback(result, message);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
send('[API Monitor] - ERROR: ' + clazz + "." + method + "[\"Error\"] => " + err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Java.performNow(function () {
|
||||
apis.forEach(function (api, _) {
|
||||
hook(api, function (originalResult, message) {
|
||||
/*if (!message.name.includes('Database') &&
|
||||
!message.name.includes('Crypto - Hash') &&
|
||||
!message.name.includes('File IO - Shared Preferences') &&
|
||||
!message.name.includes('File IO') &&
|
||||
!message.name.includes('IPC')) {
|
||||
*/
|
||||
message.returnValue = originalResult
|
||||
if (originalResult) {
|
||||
if (typeof originalResult === 'object') {
|
||||
message.returnValue = '' + message.returnValue
|
||||
}
|
||||
}
|
||||
if (!message.result)
|
||||
message.result = ''
|
||||
if (!message.returnValue)
|
||||
message.returnValue = ''
|
||||
var msg = 'MobSF-API-Monitor: ' + JSON.stringify(message);
|
||||
send(msg + ',');
|
||||
// }
|
||||
return originalResult;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
Java.perform(function () {
|
||||
// send("[Debugger Check Bypass] Activated");
|
||||
var Debug = Java.use('android.os.Debug');
|
||||
Debug.isDebuggerConnected.implementation = function () {
|
||||
send('[Debugger Check Bypass] isDebuggerConnected() bypassed');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,255 @@
|
||||
// Based on https://codeshare.frida.re/@dzonerzy/fridantiroot/
|
||||
Java.performNow(function () {
|
||||
var RootPackages = ["com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu",
|
||||
"com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.koushikdutta.rommanager",
|
||||
"com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch",
|
||||
"com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.devadvance.rootcloak", "com.devadvance.rootcloakplus",
|
||||
"de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot",
|
||||
"com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot", "me.phh.superuser",
|
||||
"eu.chainfire.supersu.pro", "com.kingouser.com"
|
||||
];
|
||||
var RootBinaries = ["mu", ".su", "su", "busybox", "supersu", "Superuser.apk", "KingoUser.apk", "SuperSu.apk"];
|
||||
var RootProperties = {
|
||||
"ro.build.selinux": "1",
|
||||
"ro.debuggable": "0",
|
||||
"service.adb.root": "0",
|
||||
"ro.secure": "1"
|
||||
};
|
||||
var RootPropertiesKeys = [];
|
||||
for (var k in RootProperties) RootPropertiesKeys.push(k);
|
||||
|
||||
// Patch Native functions early
|
||||
|
||||
// File.exists check
|
||||
var NativeFile = Java.use('java.io.File');
|
||||
NativeFile.exists.implementation = function () {
|
||||
var name = NativeFile.getName.call(this);
|
||||
if (RootBinaries.indexOf(name) > -1) {
|
||||
send("[RootDetection Bypass] return value for binary: " + name);
|
||||
return false;
|
||||
} else {
|
||||
return this.exists.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
// String.contains check
|
||||
var String = Java.use('java.lang.String');
|
||||
String.contains.implementation = function (name) {
|
||||
if (name == "test-keys") {
|
||||
send("[RootDetection Bypass] test-keys check");
|
||||
return false;
|
||||
}
|
||||
return this.contains.call(this, name);
|
||||
};
|
||||
|
||||
// Runtime.exec check
|
||||
function isRootCheck(cmd) {
|
||||
var fakeCmd;
|
||||
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
|
||||
fakeCmd = "grep";
|
||||
send("[RootDetection Bypass] " + cmd + " command");
|
||||
return fakeCmd;
|
||||
}
|
||||
if (cmd == "su") {
|
||||
fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
|
||||
send("[RootDetection Bypass] " + cmd + " command");
|
||||
return fakeCmd;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Get all implementations
|
||||
function get_implementations(toHook) {
|
||||
var imp_args = []
|
||||
toHook.overloads.forEach(function (impl, _) {
|
||||
if (impl.hasOwnProperty('argumentTypes')) {
|
||||
var args = [];
|
||||
var argTypes = impl.argumentTypes
|
||||
argTypes.forEach(function (arg_type, __) {
|
||||
args.push(arg_type.className)
|
||||
});
|
||||
imp_args.push(args);
|
||||
}
|
||||
});
|
||||
return imp_args;
|
||||
}
|
||||
|
||||
var Runtime = Java.use('java.lang.Runtime');
|
||||
var execImplementations = get_implementations(Runtime.exec)
|
||||
var exec = Runtime.exec.overload('java.lang.String')
|
||||
|
||||
execImplementations.forEach(function (args, _) {
|
||||
Runtime.exec.overload.apply(null, args).implementation = function () {
|
||||
var fakeCmd;
|
||||
var argz = [].slice.call(arguments);
|
||||
var cmd = argz[0]
|
||||
if (typeof cmd === 'string') {
|
||||
fakeCmd = isRootCheck(cmd);
|
||||
if (fakeCmd) {
|
||||
send("[RootDetection Bypass] " + cmd + " command");
|
||||
return exec.call(this, fakeCmd);
|
||||
}
|
||||
} else if (typeof cmd === 'object') {
|
||||
for (var i = 0; i < cmd.length; i = i + 1) {
|
||||
var tmp_cmd = cmd[i];
|
||||
fakeCmd = isRootCheck(tmp_cmd);
|
||||
if (fakeCmd) {
|
||||
send("[RootDetection Bypass] " + cmd + " command");
|
||||
return exec.call(this, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
return this['exec'].apply(this, argz);
|
||||
};
|
||||
});
|
||||
|
||||
// BufferedReader checkLine check
|
||||
var BufferedReader = Java.use('java.io.BufferedReader');
|
||||
BufferedReader.readLine.overload().implementation = function () {
|
||||
var text = this.readLine.call(this);
|
||||
if (text === null) {
|
||||
// just pass , i know it's ugly as hell but test != null won't work :(
|
||||
} else {
|
||||
var shouldFakeRead = (text.indexOf("ro.build.tags=test-keys") > -1);
|
||||
if (shouldFakeRead) {
|
||||
send("[RootDetection Bypass] build.prop file read");
|
||||
text = text.replace("ro.build.tags=test-keys", "ro.build.tags=release-keys");
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// ProcessBuilder.start check
|
||||
var ProcessBuilder = Java.use('java.lang.ProcessBuilder');
|
||||
ProcessBuilder.start.implementation = function () {
|
||||
var cmd = this.command.call(this);
|
||||
var shouldModifyCommand = false;
|
||||
for (var i = 0; i < cmd.size(); i = i + 1) {
|
||||
var tmp_cmd = cmd.get(i).toString();
|
||||
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) {
|
||||
shouldModifyCommand = true;
|
||||
}
|
||||
}
|
||||
if (shouldModifyCommand) {
|
||||
send("[RootDetection Bypass] ProcessBuilder " + cmd);
|
||||
this.command.call(this, ["grep"]);
|
||||
return this.start.call(this);
|
||||
}
|
||||
if (cmd.indexOf("su") != -1) {
|
||||
send("[RootDetection Bypass] ProcessBuilder " + cmd);
|
||||
this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]);
|
||||
return this.start.call(this);
|
||||
}
|
||||
|
||||
return this.start.call(this);
|
||||
}
|
||||
|
||||
// Patch other libraries after the above ones
|
||||
var RootBypass = [{
|
||||
class: 'android.security.keystore.KeyInfo',
|
||||
method: 'isInsideSecureHardware',
|
||||
func: function () {
|
||||
send("[RootDetection Bypass] isInsideSecureHardware");
|
||||
return true;
|
||||
},
|
||||
target: 6
|
||||
}, {
|
||||
class: 'android.app.ApplicationPackageManager',
|
||||
method: 'getPackageInfo',
|
||||
arguments: ['java.lang.String', 'int'],
|
||||
func: function (pname, flags) {
|
||||
var shouldFakePackage = (RootPackages.indexOf(pname) > -1);
|
||||
if (shouldFakePackage) {
|
||||
send("[RootDetection Bypass] root check for package: " + pname);
|
||||
pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it";
|
||||
}
|
||||
return this.getPackageInfo.call(this, pname, flags);
|
||||
}
|
||||
}, {
|
||||
class: 'android.os.SystemProperties',
|
||||
method: 'get',
|
||||
arguments: ['java.lang.String'],
|
||||
func: function (name) {
|
||||
if (RootPropertiesKeys.indexOf(name) != -1) {
|
||||
send("[RootDetection Bypass] " + name);
|
||||
return RootProperties[name];
|
||||
}
|
||||
return this.get.call(this, name);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
RootBypass.forEach(function (bypass, _) {
|
||||
var toHook;
|
||||
try {
|
||||
if (bypass.target && parseInt(Java.androidVersion, 10) < bypass.target) {
|
||||
send('[RootDetection Bypass] Not Hooking unavailable class/method - ' + bypass.class + '.' + bypass.method)
|
||||
return
|
||||
}
|
||||
toHook = Java.use(bypass.class)[bypass.method];
|
||||
if (!toHook) {
|
||||
send('[RootDetection Bypass] Cannot find ' + bypass.class + '.' + bypass.method);
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
send('[RootDetection Bypass] Error ' + bypass.class + '.' + bypass.method + err);
|
||||
return
|
||||
}
|
||||
if (bypass.arguments) {
|
||||
toHook.overload.apply(null, bypass.arguments).implementation = bypass.func;
|
||||
} else {
|
||||
toHook.overload.implementation = bypass.func;
|
||||
}
|
||||
})
|
||||
|
||||
// Native Root Check Bypass
|
||||
|
||||
Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
|
||||
onEnter: function (args) {
|
||||
var path = Memory.readCString(args[0]);
|
||||
path = path.split("/");
|
||||
var executable = path[path.length - 1];
|
||||
var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1)
|
||||
if (shouldFakeReturn) {
|
||||
Memory.writeUtf8String(args[0], "/notexists");
|
||||
send("[RootDetection Bypass] native fopen");
|
||||
}
|
||||
},
|
||||
onLeave: function (retval) {
|
||||
|
||||
}
|
||||
});
|
||||
Interceptor.attach(Module.findExportByName("libc.so", "system"), {
|
||||
onEnter: function (args) {
|
||||
var cmd = Memory.readCString(args[0]);
|
||||
send("[RootDetection Bypass] SYSTEM CMD: " + cmd);
|
||||
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id") {
|
||||
send("[RootDetection Bypass] native system: " + cmd);
|
||||
Memory.writeUtf8String(args[0], "grep");
|
||||
}
|
||||
if (cmd == "su") {
|
||||
send("[RootDetection Bypass] native system: " + cmd);
|
||||
Memory.writeUtf8String(args[0], "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled");
|
||||
}
|
||||
},
|
||||
onLeave: function (retval) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
/*
|
||||
|
||||
TO IMPLEMENT:
|
||||
|
||||
Exec Family
|
||||
|
||||
int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
|
||||
int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
|
||||
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
|
||||
int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
|
||||
int execv(const char *path, char *const argv[]);
|
||||
int execve(const char *path, char *const argv[], char *const envp[]);
|
||||
int execvp(const char *file, char *const argv[]);
|
||||
int execvpe(const char *file, char *const argv[], char *const envp[]);
|
||||
|
||||
*/
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
Java.perform(function() {
|
||||
var androidVersion = parseInt(Java.androidVersion, 10)
|
||||
if (androidVersion > 6){
|
||||
try{
|
||||
// Generic SSL Pinning Bypass tested on Android 7, 7.1, 8, and 9
|
||||
// https://android.googlesource.com/platform/external/conscrypt/+/1186465/src/platform/java/org/conscrypt/TrustManagerImpl.java#391
|
||||
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
|
||||
TrustManagerImpl.checkTrustedRecursive.implementation = function(certs, host, clientAuth, untrustedChain, trustedChain, used) {
|
||||
send('[SSL Pinning Bypass] checkTrustedRecursive() bypassed');
|
||||
return Java.use("java.util.ArrayList").$new();
|
||||
}
|
||||
}catch (err) {
|
||||
send('[SSL Pinning Bypass] TrustManagerImpl.checkTrustedRecursive() not found');
|
||||
}
|
||||
try {
|
||||
var TrustManagerImpl2 = Java.use('com.android.org.conscrypt.TrustManagerImpl');
|
||||
TrustManagerImpl2.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
|
||||
send('[SSL Pinning Bypass] verifyChain() bypassed for: ' + host);
|
||||
return untrustedChain;
|
||||
}
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] TrustManagerImpl.verifyChain() not found');
|
||||
}
|
||||
try {
|
||||
var ConscryptFileDescriptorSocket = Java.use('com.android.org.conscrypt.ConscryptFileDescriptorSocket');
|
||||
ConscryptFileDescriptorSocket.verifyCertificateChain.implementation = function (certChain, authMethod) {
|
||||
send('[SSL Pinning Bypass] verifyCertificateChain() bypassed');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] ConscryptFileDescriptorSocket.verifyCertificateChain() not found');
|
||||
}
|
||||
} else if (androidVersion > 4 && androidVersion < 7) {
|
||||
// Generic SSL Pinning Bypass tested on Android 5, 5,1, 6
|
||||
// https://codeshare.frida.re/@akabe1/frida-universal-pinning-bypasser/
|
||||
try {
|
||||
var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
|
||||
OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, authMethod) {
|
||||
send('[SSL Pinning Bypass] OpenSSLSocketImpl.verifyCertificateChain() bypassed');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] OpenSSLSocketImpl.verifyCertificateChain() not found');
|
||||
}
|
||||
}
|
||||
// 3rd Party Pinning
|
||||
try{
|
||||
var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
|
||||
OkHttpClient.setCertificatePinner.implementation = function(certificatePinner){
|
||||
send('[SSL Pinning Bypass] OkHttpClient.setCertificatePinner() bypassed');
|
||||
return this;
|
||||
};
|
||||
// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
|
||||
var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
|
||||
CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(p0, p1){
|
||||
send('[SSL Pinning Bypass] CertificatePinner.check() 1 bypassed');
|
||||
return;
|
||||
};
|
||||
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(p0, p1){
|
||||
send('[SSL Pinning Bypass] CertificatePinner.check() 2 bypassed');
|
||||
return;
|
||||
};
|
||||
} catch(err) {
|
||||
send('[SSL Pinning Bypass] okhttp CertificatePinner not found');
|
||||
}
|
||||
try {
|
||||
var CertificatePinner2 = Java.use('okhttp3.CertificatePinner');
|
||||
CertificatePinner2.check.overload('java.lang.String', 'java.util.List').implementation = function (str) {
|
||||
send('[SSL Pinning Bypass] okhttp3.CertificatePinner.check() bypassed for ' + str);
|
||||
return;
|
||||
};
|
||||
} catch(err) {
|
||||
send('[SSL Pinning Bypass] okhttp3 CertificatePinner not found');
|
||||
}
|
||||
try {
|
||||
// https://gist.github.com/cubehouse/56797147b5cb22768b500f25d3888a22
|
||||
var dataTheorem = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
|
||||
dataTheorem.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (str) {
|
||||
send('[SSL Pinning Bypass] DataTheorem trustkit.pinning.OkHostnameVerifier.verify() 1 bypassed for ' + str);
|
||||
return true;
|
||||
};
|
||||
|
||||
dataTheorem.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (str) {
|
||||
send('[SSL Pinning Bypass] DataTheorem trustkit.pinning.OkHostnameVerifier.verify() 2 bypassed for ' + str);
|
||||
return true;
|
||||
};
|
||||
} catch(err) {
|
||||
send('[SSL Pinning Bypass] DataTheorem trustkit not found');
|
||||
}
|
||||
try {
|
||||
var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');
|
||||
PinningTrustManager.checkServerTrusted.implementation = function () {
|
||||
send('[SSL Pinning Bypass] Appcelerator appcelerator.https.PinningTrustManager.checkServerTrusted() bypassed');
|
||||
}
|
||||
} catch (err) {
|
||||
send('[SSL Pinning Bypass] Appcelerator PinningTrustManager not found');
|
||||
}
|
||||
try {
|
||||
var SSLCertificateChecker = Java.use('nl.xservices.plugins.SSLCertificateChecker');
|
||||
SSLCertificateChecker.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (action, args, callbackContext) {
|
||||
send('[SSL Pinning Bypass] Apache Cordova - SSLCertificateChecker.execute() bypassed');
|
||||
callbackContext.success("CONNECTION_SECURE");
|
||||
return;
|
||||
};
|
||||
} catch(err) {
|
||||
send('[SSL Pinning Bypass] Apache Cordova SSLCertificateChecker not found');
|
||||
}
|
||||
try {
|
||||
var wultra = Java.use('com.wultra.android.sslpinning.CertStore');
|
||||
wultra.validateFingerprint.overload('java.lang.String', '[B').implementation = function (commonName, fingerprint) {
|
||||
send('[SSL Pinning Bypass] Wultra com.wultra.android.sslpinning.CertStore.validateFingerprint() bypassed');
|
||||
var ValidationResult = Java.use('com.wultra.android.sslpinning.ValidationResult');
|
||||
return ValidationResult.TRUSTED;
|
||||
};
|
||||
} catch(err) {
|
||||
send('[SSL Pinning Bypass] Wultra CertStore.validateFingerprint not found');
|
||||
}
|
||||
}, 0);
|
||||
@@ -0,0 +1,162 @@
|
||||
// https://codeshare.frida.re/@dzonerzy/aesinfo/
|
||||
|
||||
Java.perform(function () {
|
||||
var use_single_byte = false;
|
||||
var complete_bytes = new Array();
|
||||
var index = 0;
|
||||
var secretKeySpecDef = Java.use('javax.crypto.spec.SecretKeySpec');
|
||||
var ivParameterSpecDef = Java.use('javax.crypto.spec.IvParameterSpec');
|
||||
var cipherDef = Java.use('javax.crypto.Cipher');
|
||||
var cipherDoFinal_1 = cipherDef.doFinal.overload();
|
||||
var cipherDoFinal_2 = cipherDef.doFinal.overload('[B');
|
||||
var cipherDoFinal_3 = cipherDef.doFinal.overload('[B', 'int');
|
||||
var cipherDoFinal_4 = cipherDef.doFinal.overload('[B', 'int', 'int');
|
||||
var cipherDoFinal_5 = cipherDef.doFinal.overload('[B', 'int', 'int', '[B');
|
||||
var cipherDoFinal_6 = cipherDef.doFinal.overload('[B', 'int', 'int', '[B', 'int');
|
||||
var cipherUpdate_1 = cipherDef.update.overload('[B');
|
||||
var cipherUpdate_2 = cipherDef.update.overload('[B', 'int', 'int');
|
||||
var cipherUpdate_3 = cipherDef.update.overload('[B', 'int', 'int', '[B');
|
||||
var cipherUpdate_4 = cipherDef.update.overload('[B', 'int', 'int', '[B', 'int');
|
||||
var secretKeySpecDef_init_1 = secretKeySpecDef.$init.overload('[B', 'java.lang.String');
|
||||
var secretKeySpecDef_init_2 = secretKeySpecDef.$init.overload('[B', 'int', 'int', 'java.lang.String');
|
||||
var ivParameterSpecDef_init_1 = ivParameterSpecDef.$init.overload('[B');
|
||||
var ivParameterSpecDef_init_2 = ivParameterSpecDef.$init.overload('[B', 'int', 'int');
|
||||
secretKeySpecDef_init_1.implementation = function (arr, alg) {
|
||||
var key = b2s(arr);
|
||||
send("Creating " + alg + " secret key, plaintext:\\n" + hexdump(key));
|
||||
return secretKeySpecDef_init_1.call(this, arr, alg);
|
||||
}
|
||||
|
||||
secretKeySpecDef_init_2.implementation = function (arr, off, len, alg) {
|
||||
var key = b2s(arr);
|
||||
send("Creating " + alg + " secret key, plaintext:\\n" + hexdump(key));
|
||||
return secretKeySpecDef_init_2.call(this, arr, off, len, alg);
|
||||
}
|
||||
|
||||
/*ivParameterSpecDef_init_1.implementation = function(arr)
|
||||
{
|
||||
var iv = b2s(arr);
|
||||
send("Creating IV:\\n" + hexdump(iv));
|
||||
return ivParameterSpecDef_init_1.call(this, arr);
|
||||
}
|
||||
|
||||
ivParameterSpecDef_init_2.implementation = function(arr, off, len)
|
||||
{
|
||||
var iv = b2s(arr);
|
||||
send("Creating IV, plaintext:\\n" + hexdump(iv));
|
||||
return ivParameterSpecDef_init_2.call(this, arr, off, len);
|
||||
}*/
|
||||
|
||||
cipherDoFinal_1.implementation = function () {
|
||||
var ret = cipherDoFinal_1.call(this);
|
||||
info(this.getIV(), this.getAlgorithm(), complete_bytes, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cipherDoFinal_2.implementation = function (arr) {
|
||||
addtoarray(arr);
|
||||
var ret = cipherDoFinal_2.call(this, arr);
|
||||
info(this.getIV(), this.getAlgorithm(), complete_bytes, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cipherDoFinal_3.implementation = function (arr, a) {
|
||||
addtoarray(arr);
|
||||
var ret = cipherDoFinal_3.call(this, arr, a);
|
||||
info(this.getIV(), this.getAlgorithm(), complete_bytes, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cipherDoFinal_4.implementation = function (arr, a, b) {
|
||||
addtoarray(arr);
|
||||
var ret = cipherDoFinal_4.call(this, arr, a, b);
|
||||
info(this.getIV(), this.getAlgorithm(), complete_bytes, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cipherDoFinal_5.implementation = function (arr, a, b, c) {
|
||||
addtoarray(arr);
|
||||
var ret = cipherDoFinal_5.call(this, arr, a, b, c);
|
||||
info(this.getIV(), this.getAlgorithm(), complete_bytes, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cipherDoFinal_6.implementation = function (arr, a, b, c, d) {
|
||||
addtoarray(arr);
|
||||
var ret = cipherDoFinal_6.call(this, arr, a, b, c, d);
|
||||
info(this.getIV(), this.getAlgorithm(), complete_bytes, c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cipherUpdate_1.implementation = function (arr) {
|
||||
addtoarray(arr);
|
||||
return cipherUpdate_1.call(this, arr);
|
||||
}
|
||||
|
||||
cipherUpdate_2.implementation = function (arr, a, b) {
|
||||
addtoarray(arr);
|
||||
return cipherUpdate_2.call(this, arr, a, b);
|
||||
}
|
||||
|
||||
cipherUpdate_3.implementation = function (arr, a, b, c) {
|
||||
addtoarray(arr);
|
||||
return cipherUpdate_3.call(this, arr, a, b, c);
|
||||
}
|
||||
|
||||
cipherUpdate_4.implementation = function (arr, a, b, c, d) {
|
||||
addtoarray(arr);
|
||||
return cipherUpdate_4.call(this, arr, a, b, c, d);
|
||||
}
|
||||
|
||||
function info(iv, alg, plain, encoded) {
|
||||
send("Performing encryption/decryption");
|
||||
if (iv) {
|
||||
send("Initialization Vector: \\n" + hexdump(b2s(iv)));
|
||||
} else {
|
||||
send("Initialization Vector: " + iv);
|
||||
}
|
||||
send("Algorithm: " + alg);
|
||||
send("In: \\n" + hexdump(b2s(plain)));
|
||||
send("Out: \\n" + hexdump(b2s(encoded)));
|
||||
complete_bytes = [];
|
||||
index = 0;
|
||||
}
|
||||
|
||||
function hexdump(buffer, blockSize) {
|
||||
blockSize = blockSize || 16;
|
||||
var lines = [];
|
||||
var hex = "0123456789ABCDEF";
|
||||
for (var b = 0; b < buffer.length; b += blockSize) {
|
||||
var block = buffer.slice(b, Math.min(b + blockSize, buffer.length));
|
||||
var addr = ("0000" + b.toString(16)).slice(-4);
|
||||
var codes = block.split('').map(function (ch) {
|
||||
var code = ch.charCodeAt(0);
|
||||
return " " + hex[(0xF0 & code) >> 4] + hex[0x0F & code];
|
||||
}).join("");
|
||||
codes += " ".repeat(blockSize - block.length);
|
||||
var chars = block.replace(/[\\x00-\\x1F\\x20]/g, '.');
|
||||
chars += " ".repeat(blockSize - block.length);
|
||||
lines.push(addr + " " + codes + " " + chars);
|
||||
}
|
||||
return lines.join("\\n");
|
||||
}
|
||||
|
||||
function b2s(array) {
|
||||
var result = "";
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
result += String.fromCharCode(modulus(array[i], 256));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function modulus(x, n) {
|
||||
return ((x % n) + n) % n;
|
||||
}
|
||||
|
||||
function addtoarray(arr) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
complete_bytes[index] = arr[i];
|
||||
index = index + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// https://github.com/iddoeldor/frida-snippets
|
||||
// Bypass FLAG_SECURE or screenshot prevention
|
||||
|
||||
Java.perform(function () {
|
||||
Java.use('android.view.SurfaceView').setSecure.overload('boolean').implementation = function (flag) {
|
||||
send('[1] flag:' + flag);
|
||||
this.call(false);
|
||||
};
|
||||
var LayoutParams = Java.use('android.view.WindowManager$LayoutParams');
|
||||
Java.use('android.view.Window').setFlags.overload('int', 'int').implementation = function (flags, mask) {
|
||||
send('flag secure: ' + LayoutParams.FLAG_SECURE.value);
|
||||
send('before:' + flags);
|
||||
flags = (flags.value & ~LayoutParams.FLAG_SECURE.value);
|
||||
send('after:' + flags);
|
||||
this.call(this, flags, mask);
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
Java.perform(function() {
|
||||
// Use send() for logging
|
||||
});
|
||||
@@ -0,0 +1,455 @@
|
||||
|
||||
/**
|
||||
It should be launch earlier in order to be aware of a maximun
|
||||
quantity of file descriptors.
|
||||
|
||||
|
||||
@author @FrenchYeti
|
||||
*/
|
||||
Java.perform(function () {
|
||||
|
||||
// ============= Config
|
||||
var CONFIG = {
|
||||
// if TRUE enable data dump
|
||||
printEnable: true,
|
||||
// if TRUE enable libc.so open/read/write hook
|
||||
printLibc: false,
|
||||
// if TRUE print the stack trace for each hook
|
||||
printStackTrace: false,
|
||||
// to filter the file path whose data want to be dumped in ASCII
|
||||
dump_ascii_If_Path_contains: [".log", ".xml", ".prop"],
|
||||
// to filter the file path whose data want to be NOT dumped in hexdump (useful for big chunk and excessive reads)
|
||||
dump_hex_If_Path_NOT_contains: [".png", "/proc/self/task", "/system/lib", "base.apk", "cacert"],
|
||||
// to filter the file path whose data want to be NOT dumped fron libc read/write (useful for big chunk and excessive reads)
|
||||
dump_raw_If_Path_NOT_contains: [".png", "/proc/self/task", "/system/lib", "base.apk", "cacert"]
|
||||
}
|
||||
|
||||
// ============= Keep a trace of file descriptor, path, and so
|
||||
var TraceFD = {};
|
||||
var TraceFS = {};
|
||||
var TraceFile = {};
|
||||
var TraceSysFD = {};
|
||||
|
||||
|
||||
// ============= Get classes
|
||||
var CLS = {
|
||||
File: Java.use("java.io.File"),
|
||||
FileInputStream: Java.use("java.io.FileInputStream"),
|
||||
FileOutputStream: Java.use("java.io.FileOutputStream"),
|
||||
String: Java.use("java.lang.String"),
|
||||
FileChannel: Java.use("java.nio.channels.FileChannel"),
|
||||
FileDescriptor: Java.use("java.io.FileDescriptor"),
|
||||
Thread: Java.use("java.lang.Thread"),
|
||||
StackTraceElement: Java.use("java.lang.StackTraceElement"),
|
||||
AndroidDbSQLite: Java.use("android.database.sqlite.SQLiteDatabase")
|
||||
};
|
||||
var File = {
|
||||
new: [
|
||||
CLS.File.$init.overload("java.io.File", "java.lang.String"),
|
||||
CLS.File.$init.overload("java.lang.String"),
|
||||
CLS.File.$init.overload("java.lang.String", "java.lang.String"),
|
||||
CLS.File.$init.overload("java.net.URI"),
|
||||
]
|
||||
};
|
||||
var FileInputStream = {
|
||||
new: [
|
||||
CLS.FileInputStream.$init.overload("java.io.File"),
|
||||
CLS.FileInputStream.$init.overload("java.io.FileDescriptor"),
|
||||
CLS.FileInputStream.$init.overload("java.lang.String"),
|
||||
],
|
||||
read: [
|
||||
CLS.FileInputStream.read.overload(),
|
||||
CLS.FileInputStream.read.overload("[B"),
|
||||
CLS.FileInputStream.read.overload("[B", "int", "int"),
|
||||
],
|
||||
};
|
||||
var FileOuputStream = {
|
||||
new: [
|
||||
CLS.FileOutputStream.$init.overload("java.io.File"),
|
||||
CLS.FileOutputStream.$init.overload("java.io.File", "boolean"),
|
||||
CLS.FileOutputStream.$init.overload("java.io.FileDescriptor"),
|
||||
CLS.FileOutputStream.$init.overload("java.lang.String"),
|
||||
CLS.FileOutputStream.$init.overload("java.lang.String", "boolean")
|
||||
],
|
||||
write: [
|
||||
CLS.FileOutputStream.write.overload("[B"),
|
||||
CLS.FileOutputStream.write.overload("int"),
|
||||
CLS.FileOutputStream.write.overload("[B", "int", "int"),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ============= Hook implementation
|
||||
|
||||
File.new[1].implementation = function (a0) {
|
||||
prettyLog("[Java::File.new.1] New file : " + a0);
|
||||
|
||||
var ret = File.new[1].call(this, a0);
|
||||
var f = Java.cast(this, CLS.File);
|
||||
TraceFile["f" + this.hashCode()] = a0;
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
File.new[2].implementation = function (a0, a1) {
|
||||
prettyLog("[Java::File.read.2] New file : " + a0 + "/" + a1);
|
||||
|
||||
var ret = File.new[2].call(this, a0, a1);;
|
||||
var f = Java.cast(this, CLS.File);
|
||||
TraceFile["f" + this.hashCode()] = a0 + "/" + a1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
FileInputStream.new[0].implementation = function (a0) {
|
||||
var file = Java.cast(a0, CLS.File);
|
||||
var fname = TraceFile["f" + file.hashCode()];
|
||||
|
||||
if (fname == null) {
|
||||
var p = file.getAbsolutePath();
|
||||
if (p !== null)
|
||||
fname = TraceFile["f" + file.hashCode()] = p;
|
||||
}
|
||||
if (fname == null)
|
||||
fname = "[unknow]"
|
||||
|
||||
prettyLog("[Java::FileInputStream.new.0] New input stream from file (" + fname + "): ");
|
||||
|
||||
var fis = FileInputStream.new[0].call(this, a0)
|
||||
var f = Java.cast(this, CLS.FileInputStream);
|
||||
|
||||
TraceFS["fd" + this.hashCode()] = fname;
|
||||
|
||||
var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
|
||||
TraceFD["fd" + fd.hashCode()] = fname;
|
||||
|
||||
return fis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FileInputStream.read[1].implementation = function (a0) {
|
||||
var fname = TraceFS["fd" + this.hashCode()];
|
||||
var fd = null;
|
||||
if (fname == null) {
|
||||
fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
fname = TraceFD["fd" + fd.hashCode()]
|
||||
}
|
||||
if (fname == null)
|
||||
fname = "[unknow]";
|
||||
|
||||
var b = Java.array('byte', a0);
|
||||
|
||||
prettyLog("[Java::FileInputStream.read.1] Read from file,offset (" + fname + "," + a0 + "):\n" +
|
||||
prettyPrint(fname, b));
|
||||
|
||||
return FileInputStream.read[1].call(this, a0);
|
||||
}
|
||||
FileInputStream.read[2].implementation = function (a0, a1, a2) {
|
||||
var fname = TraceFS["fd" + this.hashCode()];
|
||||
var fd = null;
|
||||
if (fname == null) {
|
||||
fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
fname = TraceFD["fd" + fd.hashCode()]
|
||||
}
|
||||
if (fname == null)
|
||||
fname = "[unknow]";
|
||||
|
||||
var b = Java.array('byte', a0);
|
||||
|
||||
prettyLog("[Java::FileInputStream.read.2] Read from file,offset,len (" + fname + "," + a1 + "," + a2 + ")\n" +
|
||||
prettyPrint(fname, b));
|
||||
|
||||
return FileInputStream.read[2].call(this, a0, a1, a2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============== File Output Stream ============
|
||||
|
||||
|
||||
|
||||
FileOuputStream.new[0].implementation = function (a0) {
|
||||
var file = Java.cast(a0, CLS.File);
|
||||
var fname = TraceFile["f" + file.hashCode()];
|
||||
|
||||
if (fname == null)
|
||||
fname = "[unknow]<File:" + file.hashCode() + ">";
|
||||
|
||||
|
||||
prettyLog("[Java::FileOuputStream.new.0] New output stream to file (" + fname + "): ");
|
||||
|
||||
var fis = FileOuputStream.new[0].call(this, a0);
|
||||
|
||||
TraceFS["fd" + this.hashCode()] = fname;
|
||||
|
||||
var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
TraceFD["fd" + fd.hashCode()] = fname;
|
||||
|
||||
return fis;
|
||||
}
|
||||
|
||||
FileOuputStream.new[1].implementation = function (a0) {
|
||||
var file = Java.cast(a0, CLS.File);
|
||||
var fname = TraceFile["f" + file.hashCode()];
|
||||
|
||||
if (fname == null)
|
||||
fname = "[unknow]";
|
||||
|
||||
|
||||
prettyLog("[Java::FileOuputStream.new.1] New output stream to file (" + fname + "): \n");
|
||||
|
||||
var fis = FileOuputStream.new[1].call(this, a0);
|
||||
|
||||
TraceFS["fd" + this.hashCode()] = fname;
|
||||
|
||||
var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
|
||||
TraceFD["fd" + fd.hashCode()] = fname;
|
||||
|
||||
return fis;
|
||||
}
|
||||
|
||||
FileOuputStream.new[2].implementation = function (a0) {
|
||||
var fd = Java.cast(a0, CLS.FileDescriptor);
|
||||
var fname = TraceFD["fd" + fd.hashCode()];
|
||||
|
||||
if (fname == null)
|
||||
fname = "[unknow]";
|
||||
|
||||
|
||||
prettyLog("[Java::FileOuputStream.new.2] New output stream to FileDescriptor (" + fname + "): \n");
|
||||
var fis = FileOuputStream.new[1].call(this, a0)
|
||||
|
||||
TraceFS["fd" + this.hashCode()] = fname;
|
||||
|
||||
return fis;
|
||||
}
|
||||
FileOuputStream.new[3].implementation = function (a0) {
|
||||
prettyLog("[Java::FileOuputStream.new.3] New output stream to file (str=" + a0 + "): \n");
|
||||
|
||||
var fis = FileOuputStream.new[1].call(this, a0)
|
||||
|
||||
TraceFS["fd" + this.hashCode()] = a0;
|
||||
var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
TraceFD["fd" + fd.hashCode()] = a0;
|
||||
|
||||
return fis;
|
||||
}
|
||||
FileOuputStream.new[4].implementation = function (a0) {
|
||||
prettyLog("[Java::FileOuputStream.new.4] New output stream to file (str=" + a0 + ",bool): \n");
|
||||
|
||||
var fis = FileOuputStream.new[1].call(this, a0)
|
||||
TraceFS["fd" + this.hashCode()] = a0;
|
||||
var fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
TraceFD["fd" + fd.hashCode()] = a0;
|
||||
|
||||
return fis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FileOuputStream.write[0].implementation = function (a0) {
|
||||
var fname = TraceFS["fd" + this.hashCode()];
|
||||
var fd = null;
|
||||
|
||||
if (fname == null) {
|
||||
fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
fname = TraceFD["fd" + fd.hashCode()]
|
||||
}
|
||||
if (fname == null)
|
||||
fname = "[unknow]";
|
||||
|
||||
prettyLog("[Java::FileOuputStream.write.0] Write byte array (" + fname + "):\n" +
|
||||
prettyPrint(fname, a0));
|
||||
|
||||
return FileOuputStream.write[0].call(this, a0);
|
||||
}
|
||||
FileOuputStream.write[1].implementation = function (a0) {
|
||||
|
||||
var fname = TraceFS["fd" + this.hashCode()];
|
||||
var fd = null;
|
||||
if (fname == null) {
|
||||
fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
fname = TraceFD["fd" + fd.hashCode()]
|
||||
}
|
||||
if (fname == null)
|
||||
fname = "[unknow]";
|
||||
|
||||
prettyLog("[Java::FileOuputStream.write.1] Write int (" + fname + "): " + a0);
|
||||
|
||||
|
||||
return FileOuputStream.write[1].call(this, a0);
|
||||
}
|
||||
FileOuputStream.write[2].implementation = function (a0, a1, a2) {
|
||||
|
||||
var fname = TraceFS["fd" + this.hashCode()];
|
||||
var fd = null;
|
||||
if (fname == null) {
|
||||
fd = Java.cast(this.getFD(), CLS.FileDescriptor);
|
||||
fname = TraceFD["fd" + fd.hashCode()]
|
||||
if (fname == null)
|
||||
fname = "[unknow], fd=" + this.hashCode();
|
||||
}
|
||||
|
||||
prettyLog("[Java::FileOuputStream.write.2] Write " + a2 + " bytes from " + a1 + " (" + fname + "):\n" +
|
||||
prettyPrint(fname, a0));
|
||||
|
||||
return FileOuputStream.write[2].call(this, a0, a1, a2);
|
||||
}
|
||||
|
||||
// native hooks
|
||||
Interceptor.attach(
|
||||
Module.findExportByName("libc.so", "read"), {
|
||||
// fd, buff, len
|
||||
onEnter: function (args) {
|
||||
if (CONFIG.printLibc === true) {
|
||||
var bfr = args[1],
|
||||
sz = args[2].toInt32();
|
||||
var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]";
|
||||
|
||||
prettyLog("[Libc::read] Read FD (" + path + "," + bfr + "," + sz + ")\n" +
|
||||
rawPrint(path, Memory.readByteArray(bfr, sz)));
|
||||
}
|
||||
},
|
||||
onLeave: function (ret) {
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Interceptor.attach(
|
||||
Module.findExportByName("libc.so", "open"), {
|
||||
// path, flags, mode
|
||||
onEnter: function (args) {
|
||||
this.path = Memory.readCString(args[0]);
|
||||
},
|
||||
onLeave: function (ret) {
|
||||
TraceSysFD["fd-" + ret.toInt32()] = this.path;
|
||||
if (CONFIG.printLibc === true)
|
||||
prettyLog("[Libc::open] Open file '" + this.path + "' (fd: " + ret.toInt32() + ")");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Interceptor.attach(
|
||||
Module.findExportByName("libc.so", "write"), {
|
||||
// fd, buff, count
|
||||
onEnter: function (args) {
|
||||
if (CONFIG.printLibc === true) {
|
||||
var bfr = args[1],
|
||||
sz = args[2].toInt32();
|
||||
var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]";
|
||||
|
||||
prettyLog("[Libc::write] Write FD (" + path + "," + bfr + "," + sz + ")\n" +
|
||||
rawPrint(path, Memory.readByteArray(bfr, sz)));
|
||||
}
|
||||
},
|
||||
onLeave: function (ret) {
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
// helper functions
|
||||
function prettyLog(str) {
|
||||
send("---------------------------\n" + str);
|
||||
if (CONFIG.printStackTrace === true) {
|
||||
printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
function prettyPrint(path, buffer) {
|
||||
if (CONFIG.printEnable === false) return "";
|
||||
|
||||
if (contains(path, CONFIG.dump_ascii_If_Path_contains)) {
|
||||
return b2s(buffer);
|
||||
} else if (!contains(path, CONFIG.dump_hex_If_Path_NOT_contains)) {
|
||||
return hexdump(b2s(buffer));
|
||||
}
|
||||
return "[dump skipped by config]";
|
||||
}
|
||||
|
||||
function rawPrint(path, buffer) {
|
||||
if (CONFIG.printEnable === false) return "";
|
||||
|
||||
if (!contains(path, CONFIG.dump_raw_If_Path_NOT_contains)) {
|
||||
return hexdump(buffer);
|
||||
}
|
||||
return "[dump skipped by config]";
|
||||
}
|
||||
|
||||
function contains(path, patterns) {
|
||||
for (var i = 0; i < patterns.length; i++)
|
||||
if (path.indexOf(patterns[i]) > -1) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function printStackTrace() {
|
||||
var th = Java.cast(CLS.Thread.currentThread(), CLS.Thread);
|
||||
var stack = th.getStackTrace(),
|
||||
e = null;
|
||||
|
||||
for (var i = 0; i < stack.length; i++) {
|
||||
send("\t" + stack[i].getClassName() + "." + stack[i].getMethodName() + "(" + stack[i].getFileName() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
function isZero(block) {
|
||||
var m = /^[0\s]+$/.exec(block);
|
||||
return m != null && m.length > 0 && (m[0] == block);
|
||||
}
|
||||
|
||||
function hexdump(buffer, blockSize) {
|
||||
blockSize = blockSize || 16;
|
||||
var lines = [];
|
||||
var hex = "0123456789ABCDEF";
|
||||
var prevZero = false,
|
||||
ctrZero = 0;
|
||||
for (var b = 0; b < buffer.length; b += blockSize) {
|
||||
var block = buffer.slice(b, Math.min(b + blockSize, buffer.length));
|
||||
var addr = ("0000" + b.toString(16)).slice(-4);
|
||||
var codes = block.split('').map(function (ch) {
|
||||
var code = ch.charCodeAt(0);
|
||||
return " " + hex[(0xF0 & code) >> 4] + hex[0x0F & code];
|
||||
}).join("");
|
||||
codes += " ".repeat(blockSize - block.length);
|
||||
var chars = block.replace(/[\\x00-\\x1F\\x20\n]/g, '.');
|
||||
chars += " ".repeat(blockSize - block.length);
|
||||
if (isZero(codes)) {
|
||||
ctrZero += blockSize;
|
||||
prevZero = true;
|
||||
} else {
|
||||
if (prevZero) {
|
||||
lines.push("\t [" + ctrZero + "] bytes of zeroes");
|
||||
}
|
||||
lines.push(addr + " " + codes + " " + chars);
|
||||
prevZero = false;
|
||||
ctrZero = 0;
|
||||
}
|
||||
}
|
||||
if (prevZero) {
|
||||
lines.push("\t [" + ctrZero + "] bytes of zeroes");
|
||||
}
|
||||
return lines.join("\\n");
|
||||
}
|
||||
|
||||
function b2s(array) {
|
||||
var result = "";
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
result += String.fromCharCode(modulus(array[i], 256));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function modulus(x, n) {
|
||||
return ((x % n) + n) % n;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
// https://github.com/iddoeldor/frida-snippets
|
||||
// The ANDROID_ID is unique in each application in Android.
|
||||
Java.perform(function () {
|
||||
function getContext() {
|
||||
return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext().getContentResolver();
|
||||
}
|
||||
send('[-]' + Java.use('android.provider.Settings$Secure').getString(getContext(), 'android_id'));
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
// Android: Java bytearray dumping
|
||||
// https://awakened1712.github.io/hacking/hacking-frida/
|
||||
function bytes2hex(array) {
|
||||
var result = '';
|
||||
for (var i = 0; i < array.length; ++i)
|
||||
result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2);
|
||||
result += ' (' + array.length + ' bytes)'
|
||||
return result;
|
||||
}
|
||||
|
||||
function jhexdump(array) {
|
||||
var ptr = Memory.alloc(array.length);
|
||||
for (var i = 0; i < array.length; ++i)
|
||||
Memory.writeS8(ptr.add(i), array[i]);
|
||||
send(hexdump(ptr, { offset: 0, length: array.length, header: false, ansi: false }));
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// https://github.com/iddoeldor/frida-snippets#hook-constructor
|
||||
Java.performNow(function () {
|
||||
Java.use('java.lang.StringBuilder').$init.overload('java.lang.String').implementation = function (stringArgument) {
|
||||
send("c'tor");
|
||||
return this.$init(stringArgument);
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// https://github.com/iddoeldor/frida-snippets
|
||||
Java.performNow(function () {
|
||||
Java.use('java.lang.reflect.Method').invoke.overload('java.lang.Object', '[Ljava.lang.Object;').implementation = function (a, b) {
|
||||
send('hooked ' + a + ' ' + b);
|
||||
return this.invoke(a, b);
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
function binaryToHexToAscii(array, readLimit) {
|
||||
var result = [];
|
||||
// read 100 bytes #performance
|
||||
readLimit = readLimit || 100;
|
||||
for (var i = 0; i < readLimit; ++i) {
|
||||
result.push(String.fromCharCode( // hex2ascii part
|
||||
parseInt(
|
||||
('0' + (array[i] & 0xFF).toString(16)).slice(-2), // binary2hex part
|
||||
16
|
||||
)
|
||||
));
|
||||
}
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
function hookInputStream() {
|
||||
Java.use('java.io.InputStream')['read'].overload('[B').implementation = function (b) {
|
||||
// execute original and save return value
|
||||
var retval = this.read(b);
|
||||
var resp = binaryToHexToAscii(b);
|
||||
// conditions to not print garbage packets
|
||||
var reExcludeList = new RegExp(['Mmm'/*, 'Ping' /*, ' Yo'*/].join('|'));
|
||||
if (!reExcludeList.test(resp)) {
|
||||
send(resp);
|
||||
}
|
||||
var reIncludeList = new RegExp(['AAA', 'BBB', 'CCC'].join('|'));
|
||||
if (reIncludeList.test(resp)) {
|
||||
send(binaryToHexToAscii(b, 1200));
|
||||
}
|
||||
return retval;
|
||||
};
|
||||
}
|
||||
|
||||
Java.perform(hookInputStream);
|
||||
@@ -0,0 +1,28 @@
|
||||
// https://github.com/iddoeldor/frida-snippets#reveal-native-methods
|
||||
// Hook JNI by address
|
||||
// Hook native method by module name and method address and print arguments.
|
||||
var moduleName = "libGLESv2.so";
|
||||
var nativeFuncAddr = 0x1234; // $ nm --demangle --dynamic libfoo.so | grep "Class::method("
|
||||
|
||||
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
|
||||
onEnter: function (args) {
|
||||
this.lib = Memory.readUtf8String(args[0]);
|
||||
send("dlopen called with: " + this.lib);
|
||||
},
|
||||
onLeave: function (retval) {
|
||||
if (this.lib.endsWith(moduleName)) {
|
||||
send("ret: " + retval);
|
||||
var baseAddr = Module.findBaseAddress(moduleName);
|
||||
Interceptor.attach(baseAddr.add(nativeFuncAddr), {
|
||||
onEnter: function (args) {
|
||||
send("[-] hook invoked");
|
||||
send(JSON.stringify({
|
||||
a1: args[1].toInt32(),
|
||||
a2: Memory.readUtf8String(Memory.readPointer(args[2])),
|
||||
a3: Boolean(args[3])
|
||||
}, null, '\t'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* raptor_frida_android_*.js - Frida snippets for Android
|
||||
* Copyright (c) 2017 Marco Ivaldi <[email protected]>
|
||||
*
|
||||
* Frida.re JS script snippets for Android instrumentation.
|
||||
* See https://www.frida.re/ and https://codeshare.frida.re/
|
||||
* for further information on this powerful tool.
|
||||
*
|
||||
* "We want to help others achieve interop through reverse
|
||||
* engineering" -- @oleavr
|
||||
*
|
||||
* Many thanks to Maurizio Agazzini <[email protected]>
|
||||
* and Federico Dotta <[email protected]>.
|
||||
*/
|
||||
Java.perform(function () {
|
||||
// Low-level intercept and backtrace example
|
||||
Interceptor.attach(Module.findExportByName("/system/lib/libc.so", "open"), {
|
||||
onEnter: function (args) {
|
||||
// debug only the intended calls
|
||||
this.flag = false;
|
||||
// var filename = Memory.readCString(ptr(args[0]));
|
||||
// if (filename.indexOf("XYZ") === -1 && filename.indexOf("ZYX") === -1) // exclusion list
|
||||
// if (filename.indexOf("my.interesting.file") !== -1) // inclusion list
|
||||
this.flag = true;
|
||||
if (this.flag) {
|
||||
var filename = Memory.readCString(ptr(args[0]));
|
||||
send("\nfile name: " + filename);
|
||||
// print backtrace
|
||||
send("\nBacktrace:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE)
|
||||
.map(DebugSymbol.fromAddress).join("\n"));
|
||||
}
|
||||
},
|
||||
onLeave: function (retval) {
|
||||
if (this.flag) {
|
||||
// print retval
|
||||
send("\nretval: " + retval);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,116 +0,0 @@
|
||||
# begin build properties
|
||||
# autogenerated by buildinfo.sh
|
||||
ro.build.id=KTU84P
|
||||
ro.build.display.id=KTU84P
|
||||
ro.build.version.incremental=1227136
|
||||
ro.build.version.sdk=19
|
||||
ro.build.version.codename=REL
|
||||
ro.build.version.release=4.4.4
|
||||
ro.build.date=Fri Jun 13 07:05:49 UTC 2014
|
||||
ro.build.date.utc=1402643149
|
||||
ro.build.type=user
|
||||
ro.build.user=android-build
|
||||
ro.build.host=kpfj3.cbf.corp.google.com
|
||||
ro.build.tags=release-keys
|
||||
ro.product.model=Nexus 5
|
||||
ro.product.brand=google
|
||||
ro.product.name=hammerhead
|
||||
ro.product.device=hammerhead
|
||||
ro.product.board=hammerhead
|
||||
ro.product.cpu.abi=armeabi-v7a
|
||||
ro.product.cpu.abi2=armeabi
|
||||
ro.product.manufacturer=LGE
|
||||
ro.product.locale.language=en
|
||||
ro.product.locale.region=US
|
||||
ro.wifi.channels=
|
||||
ro.board.platform=msm8974
|
||||
# ro.build.product is obsolete; use ro.product.device
|
||||
ro.build.product=hammerhead
|
||||
# Do not try to parse ro.build.description or .fingerprint
|
||||
ro.build.description=hammerhead-user 4.4.4 KTU84P 1227136 release-keys
|
||||
ro.build.fingerprint=google/hammerhead/hammerhead:4.4.4/KTU84P/1227136:user/release-keys
|
||||
ro.build.characteristics=nosdcard
|
||||
# end build properties
|
||||
|
||||
#
|
||||
# ADDITIONAL_BUILD_PROPERTIES
|
||||
#
|
||||
ro.config.ringtone=Titania.ogg
|
||||
ro.config.notification_sound=Tethys.ogg
|
||||
ro.config.alarm_alert=Oxygen.ogg
|
||||
ro.com.android.dateformat=MM-dd-yyyy
|
||||
ro.com.android.dataroaming=false
|
||||
ro.url.legal=http://www.google.com/intl/%s/mobile/android/basic/phone-legal.html
|
||||
ro.url.legal.android_privacy=http://www.google.com/intl/%s/mobile/android/basic/privacy.html
|
||||
ro.com.google.clientidbase=android-google
|
||||
ro.carrier=unknown
|
||||
ro.com.android.wifi-watchlist=GoogleGuest
|
||||
ro.error.receiver.system.apps=com.google.android.gms
|
||||
ro.setupwizard.enterprise_mode=1
|
||||
ro.opengles.version=196608
|
||||
ro.sf.lcd_density=480
|
||||
persist.hwc.mdpcomp.enable=true
|
||||
ro.hwui.texture_cache_size=72
|
||||
ro.hwui.layer_cache_size=48
|
||||
ro.hwui.r_buffer_cache_size=8
|
||||
ro.hwui.path_cache_size=32
|
||||
ro.hwui.gradient_cache_size=1
|
||||
ro.hwui.drop_shadow_cache_size=6
|
||||
ro.hwui.texture_cache_flushrate=0.4
|
||||
ro.hwui.text_small_cache_width=1024
|
||||
ro.hwui.text_small_cache_height=1024
|
||||
ro.hwui.text_large_cache_width=2048
|
||||
ro.hwui.text_large_cache_height=1024
|
||||
drm.service.enabled=true
|
||||
ro.qc.sensors.max_geomag_rotvec=60
|
||||
ro.qc.sensors.max_gyro_rate=200
|
||||
ro.qc.sensors.max_accel_rate=200
|
||||
ro.qc.sensors.max_grav=200
|
||||
ro.qc.sensors.max_rotvec=200
|
||||
ro.qc.sensors.max_ortn=200
|
||||
ro.qc.sensors.max_linacc=200
|
||||
ro.qc.sensors.max_gamerv_rate=200
|
||||
ro.qualcomm.sensors.smd=true
|
||||
ro.qualcomm.sensors.game_rv=true
|
||||
ro.qualcomm.sensors.georv=true
|
||||
ro.qc.sensors.smgr_mag_cal_en=true
|
||||
ro.qc.sensors.step_detector=true
|
||||
ro.qc.sensors.step_counter=true
|
||||
debug.qualcomm.sns.hal=w
|
||||
debug.qualcomm.sns.daemon=w
|
||||
debug.qualcomm.sns.libsensor1=w
|
||||
ro.telephony.call_ring.multiple=0
|
||||
wifi.interface=wlan0
|
||||
wifi.supplicant_scan_interval=15
|
||||
media.aac_51_output_enabled=true
|
||||
persist.radio.apm_sim_not_pwdn=1
|
||||
ro.telephony.default_network=10
|
||||
telephony.lteOnCdmaDevice=1
|
||||
persist.radio.mode_pref_nv10=1
|
||||
persist.audio.handset.mic.type=digital
|
||||
persist.audio.dualmic.config=endfire
|
||||
persist.audio.fluence.voicecall=true
|
||||
persist.audio.fluence.voicerec=false
|
||||
persist.audio.fluence.speaker=false
|
||||
af.resampler.quality=4
|
||||
persist.radio.custom_ecc=1
|
||||
persist.radio.always_send_plmn=true
|
||||
ro.input.noresample=1
|
||||
dalvik.vm.heapstartsize=8m
|
||||
dalvik.vm.heapgrowthlimit=192m
|
||||
dalvik.vm.heapsize=512m
|
||||
dalvik.vm.heaptargetutilization=0.75
|
||||
dalvik.vm.heapminfree=512k
|
||||
dalvik.vm.heapmaxfree=8m
|
||||
keyguard.no_require_sim=true
|
||||
ro.facelock.black_timeout=400
|
||||
ro.facelock.det_timeout=1500
|
||||
ro.facelock.rec_timeout=2500
|
||||
ro.facelock.lively_timeout=2500
|
||||
ro.facelock.est_max_time=600
|
||||
ro.facelock.use_intro_anim=false
|
||||
persist.sys.dalvik.vm.lib=libdvm.so
|
||||
dalvik.vm.dexopt-flags=m=y
|
||||
net.bt.name=Android
|
||||
dalvik.vm.stack-trace-file=/data/anr/traces.txt
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
Processor : ARMv7 Processor rev 0 (v7l)
|
||||
BogoMIPS : 366.18
|
||||
Features : swp half thumb fastmult vfp edsp neon vfpv3
|
||||
CPU implementer : 0x41
|
||||
CPU architecture: 7
|
||||
CPU variant : 0x0
|
||||
CPU part : 0xc08
|
||||
CPU revision : 0
|
||||
|
||||
Hardware : Qualcomm MSM 8974 HAMMERHEAD (Flattened Device Tree)
|
||||
Revision : 0000
|
||||
Serial : 0000000000000000
|
||||
@@ -1,12 +0,0 @@
|
||||
/dev/tty /dev/tty 5 0 system:/dev/tty
|
||||
/dev/console /dev/console 5 1 system:console
|
||||
/dev/ptmx /dev/ptmx 5 2 system
|
||||
/dev/vc/0 /dev/vc/0 4 0 system:vtmaster
|
||||
rfcomm /dev/rfcomm 216 0-255 serial
|
||||
acm /dev/ttyACM 166 0-31 serial
|
||||
hso /dev/ttyHS 243 0-255 serial
|
||||
msm_serial_hsl /dev/ttyHSL 247 0-2 serial
|
||||
msm_serial_hs /dev/ttyHS 248 0-255 serial
|
||||
pty_slave /dev/pts 136 0-1048575 pty:slave
|
||||
pty_master /dev/ptm 128 0-1048575 pty:master
|
||||
unknown /dev/tty 4 1-63 console
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
MobSF-Frida
|
||||
@@ -0,0 +1 @@
|
||||
MobSF-Xposed
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
{
|
||||
"hookConfigs": [
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getDeviceId",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getSubscriberId",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getLine1Number",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getNetworkOperator",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getNetworkOperatorName",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getSimOperatorName",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.net.wifi.WifiInfo",
|
||||
"method": "getMacAddress",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getSimCountryIso",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getSimSerialNumber",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getNetworkCountryIso",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "getDeviceSoftwareVersion",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.os.Debug",
|
||||
"method": "isDebuggerConnected",
|
||||
"thisObject": false,
|
||||
"type": "fingerprint"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.SharedPreferencesImpl$EditorImpl",
|
||||
"method": "putString",
|
||||
"thisObject": false,
|
||||
"type": "globals"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.SharedPreferencesImpl$EditorImpl",
|
||||
"method": "putBoolean",
|
||||
"thisObject": false,
|
||||
"type": "globals"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.SharedPreferencesImpl$EditorImpl",
|
||||
"method": "putInt",
|
||||
"thisObject": false,
|
||||
"type": "globals"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.SharedPreferencesImpl$EditorImpl",
|
||||
"method": "putLong",
|
||||
"thisObject": false,
|
||||
"type": "globals"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.SharedPreferencesImpl$EditorImpl",
|
||||
"method": "putFloat",
|
||||
"thisObject": false,
|
||||
"type": "globals"
|
||||
},
|
||||
{
|
||||
"class_name": "android.content.ContentValues",
|
||||
"method": "put",
|
||||
"thisObject": false,
|
||||
"type": "globals"
|
||||
},
|
||||
{
|
||||
"class_name": "java.net.URL",
|
||||
"method": "openConnection",
|
||||
"thisObject": true,
|
||||
"type": "network"
|
||||
},
|
||||
{
|
||||
"class_name": "org.apache.http.impl.client.AbstractHttpClient",
|
||||
"method": "execute",
|
||||
"thisObject": false,
|
||||
"type": "network"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.ContextImpl",
|
||||
"method": "registerReceiver",
|
||||
"thisObject": false,
|
||||
"type": "binder"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.ActivityThread",
|
||||
"method": "handleReceiver",
|
||||
"thisObject": false,
|
||||
"type": "binder"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.Activity",
|
||||
"method": "startActivity",
|
||||
"thisObject": false,
|
||||
"type": "binder"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.BaseDexClassLoader",
|
||||
"method": "findResource",
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.BaseDexClassLoader",
|
||||
"method": "findLibrary",
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.DexFile",
|
||||
"method": "loadDex",
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.DexClassLoader",
|
||||
"method": null,
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.BaseDexClassLoader",
|
||||
"method": "findResources",
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.DexFile",
|
||||
"method": "loadClass",
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.DexFile",
|
||||
"method": null,
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "dalvik.system.PathClassLoader",
|
||||
"method": null,
|
||||
"thisObject": false,
|
||||
"type": "dex"
|
||||
},
|
||||
{
|
||||
"class_name": "java.lang.reflect.Method",
|
||||
"method": "invoke",
|
||||
"thisObject": false,
|
||||
"type": "reflection"
|
||||
},
|
||||
{
|
||||
"class_name": "javax.crypto.spec.SecretKeySpec",
|
||||
"method": null,
|
||||
"thisObject": false,
|
||||
"type": "crypto"
|
||||
},
|
||||
{
|
||||
"class_name": "javax.crypto.Cipher",
|
||||
"method": "doFinal",
|
||||
"thisObject": true,
|
||||
"type": "crypto"
|
||||
},
|
||||
{
|
||||
"class_name": "javax.crypto.Mac",
|
||||
"method": "doFinal",
|
||||
"thisObject": false,
|
||||
"type": "crypto"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.ApplicationPackageManager",
|
||||
"method": "setComponentEnabledSetting",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.NotificationManager",
|
||||
"method": "notify",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.util.Base64",
|
||||
"method": "decode",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.TelephonyManager",
|
||||
"method": "listen",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.util.Base64",
|
||||
"method": "encode",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.util.Base64",
|
||||
"method": "encodeToString",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.net.ConnectivityManager",
|
||||
"method": "setMobileDataEnabled",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.content.BroadcastReceiver",
|
||||
"method": "abortBroadcast",
|
||||
"thisObject": false,
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.SmsManager",
|
||||
"method": "sendTextMessage",
|
||||
"thisObject": false,
|
||||
"type": "sms"
|
||||
},
|
||||
{
|
||||
"class_name": "android.telephony.SmsManager",
|
||||
"method": "sendMultipartTextMessage",
|
||||
"thisObject": false,
|
||||
"type": "sms"
|
||||
},
|
||||
{
|
||||
"class_name": "java.lang.Runtime",
|
||||
"method": "exec",
|
||||
"thisObject": false,
|
||||
"type": "runtime"
|
||||
},
|
||||
{
|
||||
"class_name": "java.lang.ProcessBuilder",
|
||||
"method": "start",
|
||||
"thisObject": true,
|
||||
"type": "runtime"
|
||||
},
|
||||
{
|
||||
"class_name": "java.io.FileOutputStream",
|
||||
"method": "write",
|
||||
"thisObject": false,
|
||||
"type": "runtime"
|
||||
},
|
||||
{
|
||||
"class_name": "java.io.FileInputStream",
|
||||
"method": "read",
|
||||
"thisObject": false,
|
||||
"type": "runtime"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.ActivityManager",
|
||||
"method": "killBackgroundProcesses",
|
||||
"thisObject": false,
|
||||
"type": "runtime"
|
||||
},
|
||||
{
|
||||
"class_name": "android.os.Process",
|
||||
"method": "killProcess",
|
||||
"thisObject": false,
|
||||
"type": "runtime"
|
||||
},
|
||||
{
|
||||
"class_name": "android.content.ContentResolver",
|
||||
"method": "query",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.content.ContentResolver",
|
||||
"method": "registerContentObserver",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.content.ContentResolver",
|
||||
"method": "insert",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.accounts.AccountManager",
|
||||
"method": "getAccountsByType",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.accounts.AccountManager",
|
||||
"method": "getAccounts",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.location.Location",
|
||||
"method": "getLatitude",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.location.Location",
|
||||
"method": "getLongitude",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.content.ContentResolver",
|
||||
"method": "delete",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.media.AudioRecord",
|
||||
"method": "startRecording",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.media.MediaRecorder",
|
||||
"method": "start",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.os.SystemProperties",
|
||||
"method": "get",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "android.app.ApplicationPackageManager",
|
||||
"method": "getInstalledPackages",
|
||||
"thisObject": false,
|
||||
"type": "content"
|
||||
},
|
||||
{
|
||||
"class_name": "libcore.io.IoBridge",
|
||||
"method": "open",
|
||||
"thisObject": false,
|
||||
"type": "file"
|
||||
}
|
||||
],
|
||||
"trace": false
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,68 +1,72 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import capfuzz as cp
|
||||
|
||||
import requests
|
||||
|
||||
from MobSF.utils import is_file_exists, upstream_proxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
"""
|
||||
from capfuzz.__main__ import (
|
||||
CapFuzz
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
def stop_capfuzz(port):
|
||||
"""Capfuzz Kill."""
|
||||
# Invoke CapFuzz UI Kill Request
|
||||
def stop_httptools(port):
|
||||
"""Kill httptools."""
|
||||
# Invoke HTTPtools UI Kill Request
|
||||
try:
|
||||
requests.get('http://127.0.0.1:' + str(port) + '/kill', timeout=5)
|
||||
logger.info('Killing CapFuzz UI')
|
||||
logger.info('Killing httptools UI')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Inkoke CapFuzz Proxy Kill Request
|
||||
# Inkoke HTTPtools Proxy Kill Request
|
||||
try:
|
||||
http_proxy = 'http://127.0.0.1:' + str(port)
|
||||
headers = {'capfuzz': 'kill'}
|
||||
headers = {'httptools': 'kill'}
|
||||
url = 'http://127.0.0.1'
|
||||
requests.get(url, headers=headers, proxies={
|
||||
'http': http_proxy})
|
||||
logger.info('Killing CapFuzz Proxy')
|
||||
logger.info('Killing httptools Proxy')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def start_proxy(port, project):
|
||||
"""Start CapFuzz in Proxy Mode."""
|
||||
subprocess.Popen(['capfuzz',
|
||||
'-m', 'capture', '-p', str(port), '-n', project])
|
||||
"""
|
||||
capfuzz_obj = CapFuzz()
|
||||
proxy_trd = Thread(target=capfuzz_obj.start_proxy,
|
||||
args=(port, 'capture', project,))
|
||||
proxy_trd.daemon = True
|
||||
proxy_trd.start()
|
||||
"""
|
||||
"""Start HTTPtools in Proxy Mode."""
|
||||
argz = ['httptools',
|
||||
'-m', 'capture',
|
||||
'-p', str(port), '-n', project]
|
||||
proxies, _ = upstream_proxy('http')
|
||||
if proxies['http']:
|
||||
argz.extend(['-u', proxies['http']])
|
||||
fnull = open(os.devnull, 'w')
|
||||
subprocess.Popen(argz, stdout=fnull, stderr=subprocess.STDOUT)
|
||||
|
||||
|
||||
def start_fuzz_ui(port):
|
||||
"""Start Fuzz UI."""
|
||||
subprocess.Popen(['capfuzz',
|
||||
'-m', 'fuzz', '-p', str(port)])
|
||||
def start_httptools_ui(port):
|
||||
"""Start Server UI."""
|
||||
subprocess.Popen(['httptools',
|
||||
'-m', 'server', '-p', str(port)])
|
||||
time.sleep(3)
|
||||
"""
|
||||
capfuzz_obj = CapFuzz()
|
||||
ui_trd = Thread(target=capfuzz_obj.run_fuzz_server, args=(port,))
|
||||
ui_trd.daemon = True
|
||||
ui_trd.start()
|
||||
"""
|
||||
|
||||
|
||||
def create_ca():
|
||||
"""Generate CA on first run."""
|
||||
argz = ['mitmdump', '-n']
|
||||
subprocess.Popen(argz,
|
||||
stdin=None,
|
||||
stdout=None,
|
||||
stderr=None,
|
||||
close_fds=True)
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def get_ca_dir():
|
||||
"""Get CA Dir."""
|
||||
capfuzz_dir = os.path.dirname(cp.__file__)
|
||||
return os.path.join(capfuzz_dir, 'ca', 'mitmproxy-ca-cert.cer')
|
||||
from mitmproxy import ctx
|
||||
ca_dir = Path(ctx.mitmproxy.options.CONF_DIR).expanduser()
|
||||
ca_file = os.path.join(str(ca_dir), 'mitmproxy-ca-cert.cer')
|
||||
if not is_file_exists(ca_file):
|
||||
create_ca()
|
||||
return ca_file
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Perform Analysis on Dynamic Analysis Data."""
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@@ -9,290 +8,233 @@ import shutil
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
|
||||
from django.utils.html import escape
|
||||
from MobSF.utils import (is_file_exists,
|
||||
is_pipe_or_link,
|
||||
python_list)
|
||||
|
||||
from StaticAnalyzer.models import StaticAnalyzerAndroid
|
||||
|
||||
from MalwareAnalyzer.views.domain_check import malware_check
|
||||
|
||||
from MobSF.utils import is_base64, python_list
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def api_analysis(package, location):
|
||||
"""API Analysis."""
|
||||
api_analysis_result = {}
|
||||
logger.info('Dynamic API Analysis')
|
||||
dat = ''
|
||||
api_base64 = []
|
||||
api_fileio = []
|
||||
api_reflect = []
|
||||
api_sysprop = []
|
||||
api_cntvl = []
|
||||
api_binder = []
|
||||
api_crypto = []
|
||||
api_acntmnger = []
|
||||
api_deviceinfo = []
|
||||
api_net = []
|
||||
api_dexloader = []
|
||||
api_cmd = []
|
||||
api_sms = []
|
||||
try:
|
||||
with open(location, 'r', encoding='utf-8') as flip:
|
||||
dat = flip.readlines()
|
||||
res_id = 'Droidmon-apimonitor-' + package + ':'
|
||||
for line in dat:
|
||||
if res_id in line:
|
||||
_, value = line.split(res_id, 1)
|
||||
try:
|
||||
apis = json.loads(value, strict=False)
|
||||
ret = ''
|
||||
args = ''
|
||||
mtd = str(apis['method'])
|
||||
clss = str(apis['class'])
|
||||
if apis.get('return'):
|
||||
ret = str(apis['return'])
|
||||
else:
|
||||
ret = 'No Return Data'
|
||||
if apis.get('args'):
|
||||
args = str(apis['args'])
|
||||
else:
|
||||
args = 'No Arguments Passed'
|
||||
# XSS Safe
|
||||
call_data = ('</br>METHOD: {}'
|
||||
'</br>ARGUMENTS: {}'
|
||||
'</br>RETURN DATA: {}').format(
|
||||
escape(mtd),
|
||||
escape(args),
|
||||
escape(ret))
|
||||
|
||||
if re.findall('android.util.Base64', clss):
|
||||
# Base64 Decode
|
||||
if 'decode' in mtd:
|
||||
args_list = python_list(args)
|
||||
if is_base64(args_list[0]):
|
||||
call_data += ('</br><span class='
|
||||
'"label label - info">'
|
||||
'Decoded String'
|
||||
':</span> {}').format(
|
||||
escape(base64.b64decode(
|
||||
args_list[0])))
|
||||
api_base64.append(call_data)
|
||||
if re.findall('libcore.io|'
|
||||
'android.app.SharedP'
|
||||
'referencesImpl\\$EditorImpl', clss):
|
||||
api_fileio.append(call_data)
|
||||
if re.findall('java.lang.reflect', clss):
|
||||
api_reflect.append(call_data)
|
||||
if re.findall('android.content.ContentResolver|'
|
||||
'android.location.Location|'
|
||||
'android.media.AudioRecord|'
|
||||
'android.media.MediaRecorder|'
|
||||
'android.os.SystemProperties', clss):
|
||||
api_sysprop.append(call_data)
|
||||
if re.findall('android.app.Activity|'
|
||||
'android.app.ContextImpl|'
|
||||
'android.app.ActivityThread', clss):
|
||||
api_binder.append(call_data)
|
||||
if re.findall('javax.crypto.spec.SecretKeySpec|'
|
||||
'javax.crypto.Cipher|'
|
||||
'javax.crypto.Mac', clss):
|
||||
api_crypto.append(call_data)
|
||||
if re.findall('android.accounts.AccountManager|'
|
||||
'android.app.ApplicationPackageManager|'
|
||||
'android.app.NotificationManager|'
|
||||
'android.net.ConnectivityManager|'
|
||||
'android.content.BroadcastReceiver', clss):
|
||||
api_acntmnger.append(call_data)
|
||||
if re.findall('android.telephony.TelephonyManager|'
|
||||
'android.net.wifi.WifiInfo|'
|
||||
'android.os.Debug', clss):
|
||||
api_deviceinfo.append(call_data)
|
||||
if re.findall('dalvik.system.BaseDexClassLoader|'
|
||||
'dalvik.system.DexFile|'
|
||||
'dalvik.system.DexClassLoader|'
|
||||
'dalvik.system.PathClassLoader', clss):
|
||||
api_dexloader.append(call_data)
|
||||
if re.findall('java.lang.Runtime|java.lang.ProcessBuilder|'
|
||||
'java.io.FileOutputStream|'
|
||||
'java.io.FileInputStream|'
|
||||
'android.os.Process', clss):
|
||||
api_cmd.append(call_data)
|
||||
if re.findall('android.content.ContentValues', clss):
|
||||
api_cntvl.append(call_data)
|
||||
if re.findall('android.telephony.SmsManager', clss):
|
||||
api_sms.append(call_data)
|
||||
if re.findall('java.net.URL|org.apache.http.'
|
||||
'impl.client.AbstractHttpClient', clss):
|
||||
api_net.append(call_data)
|
||||
except Exception:
|
||||
logger.exception('Parsing JSON Failed for: %s', value)
|
||||
except Exception:
|
||||
logger.exception('Dynamic API Analysis')
|
||||
api_analysis_result['api_net'] = list(set(api_net))
|
||||
api_analysis_result['api_base64'] = list(set(api_base64))
|
||||
api_analysis_result['api_fileio'] = list(set(api_fileio))
|
||||
api_analysis_result['api_binder'] = list(set(api_binder))
|
||||
api_analysis_result['api_crypto'] = list(set(api_crypto))
|
||||
api_analysis_result['api_deviceinfo'] = list(set(api_deviceinfo))
|
||||
api_analysis_result['api_cntvl'] = list(set(api_cntvl))
|
||||
api_analysis_result['api_sms'] = list(set(api_sms))
|
||||
api_analysis_result['api_sysprop'] = list(set(api_sysprop))
|
||||
api_analysis_result['api_dexloader'] = list(set(api_dexloader))
|
||||
api_analysis_result['api_reflect'] = list(set(api_reflect))
|
||||
api_analysis_result['api_acntmnger'] = list(set(api_acntmnger))
|
||||
api_analysis_result['api_cmd'] = list(set(api_cmd))
|
||||
return api_analysis_result
|
||||
|
||||
|
||||
def run_analysis(apk_dir, md5_hash, package):
|
||||
"""Run Dynamic File Analysis."""
|
||||
analysis_result = {}
|
||||
logger.info('Dynamic File Analysis')
|
||||
capfuzz_home = os.path.join(str(Path.home()), '.capfuzz')
|
||||
web = os.path.join(capfuzz_home, 'flows', package + '.flows.txt')
|
||||
logcat = os.path.join(apk_dir, 'logcat.txt')
|
||||
xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
|
||||
traffic = ''
|
||||
web_data = ''
|
||||
xlg = ''
|
||||
domains = {}
|
||||
logcat_data = []
|
||||
clipboard = []
|
||||
# Collect Log data
|
||||
datas = get_log_data(apk_dir, package)
|
||||
clip_tag = 'I/CLIPDUMP-INFO-LOG'
|
||||
try:
|
||||
with io.open(web, mode='r', encoding='utf8', errors='ignore') as flip:
|
||||
web_data = flip.read()
|
||||
except Exception:
|
||||
pass
|
||||
with io.open(logcat, mode='r', encoding='utf8', errors='ignore') as flip:
|
||||
logcat_data = flip.readlines()
|
||||
traffic = ''.join(logcat_data)
|
||||
with io.open(xlogcat, mode='r', encoding='utf8', errors='ignore') as flip:
|
||||
xlg = flip.read()
|
||||
traffic = web_data + traffic + xlg
|
||||
for log_line in logcat_data:
|
||||
if log_line.startswith(clip_tag):
|
||||
clip_tag2 = 'I CLIPDUMP-INFO-LOG'
|
||||
# Collect Clipboard
|
||||
for log_line in datas['logcat']:
|
||||
if clip_tag in log_line:
|
||||
clipboard.append(log_line.replace(clip_tag, 'Process ID '))
|
||||
urls = []
|
||||
if clip_tag2 in log_line:
|
||||
log_line = log_line.split(clip_tag2)[1]
|
||||
clipboard.append(log_line)
|
||||
# URLs My Custom regex
|
||||
url_pattern = re.compile(
|
||||
r'((?:https?://|s?ftps?://|file://|'
|
||||
r'javascript:|data:|www\d{0,3}'
|
||||
r'[.])[\w().=/;,#:@?&~*+!$%\'{}-]+)', re.UNICODE)
|
||||
urllist = re.findall(url_pattern, traffic.lower())
|
||||
urls = re.findall(url_pattern, datas['traffic'].lower())
|
||||
if urls:
|
||||
urls = list(set(urls))
|
||||
else:
|
||||
urls = []
|
||||
# Domain Extraction and Malware Check
|
||||
logger.info('Performing Malware Check on extracted Domains')
|
||||
domains = malware_check(urllist)
|
||||
for url in urllist:
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
domains = malware_check(urls)
|
||||
|
||||
# Email Etraction Regex
|
||||
emails = []
|
||||
regex = re.compile(r'[\w.-]+@[\w-]+\.[\w.]+')
|
||||
for email in regex.findall(traffic.lower()):
|
||||
regex = re.compile(r'[\w.-]+@[\w-]+\.[\w]{2,}')
|
||||
for email in regex.findall(datas['traffic'].lower()):
|
||||
if (email not in emails) and (not email.startswith('//')):
|
||||
if email == '[email protected]':
|
||||
pass
|
||||
else:
|
||||
emails.append(email)
|
||||
# Extract Device Data
|
||||
try:
|
||||
tar_loc = os.path.join(apk_dir, package + '.tar')
|
||||
untar_dir = os.path.join(apk_dir, 'DYNAMIC_DeviceData/')
|
||||
if not os.path.exists(untar_dir):
|
||||
os.makedirs(untar_dir)
|
||||
with tarfile.open(tar_loc) as tar:
|
||||
try:
|
||||
tar.extractall(untar_dir)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception('TAR EXTRACTION FAILED')
|
||||
# Do Static Analysis on Data from Device
|
||||
xmlfiles = ''
|
||||
sqlite_db = ''
|
||||
other_files = ''
|
||||
typ = ''
|
||||
untar_dir = os.path.join(apk_dir, 'DYNAMIC_DeviceData/')
|
||||
if not os.path.exists(untar_dir):
|
||||
os.makedirs(untar_dir)
|
||||
try:
|
||||
for dir_name, _, files in os.walk(untar_dir):
|
||||
for jfile in files:
|
||||
file_path = os.path.join(untar_dir, dir_name, jfile)
|
||||
if '+' in file_path:
|
||||
shutil.move(file_path, file_path.replace('+', 'x'))
|
||||
file_path = file_path.replace('+', 'x')
|
||||
fileparam = file_path.replace(untar_dir, '')
|
||||
if jfile == 'lib':
|
||||
pass
|
||||
else:
|
||||
if jfile.endswith('.xml'):
|
||||
typ = 'xml'
|
||||
xmlfiles += ('<tr><td><a href="../View/?'
|
||||
'file={}&md5={}&type={}">{}'
|
||||
'</a></td><tr>').format(
|
||||
escape(fileparam),
|
||||
md5_hash,
|
||||
typ,
|
||||
escape(fileparam))
|
||||
else:
|
||||
with open(file_path, 'r') as flip:
|
||||
file_cnt_sig = flip.read(6)
|
||||
if file_cnt_sig == 'SQLite':
|
||||
typ = 'db'
|
||||
sqlite_db += ('<tr><td><a href="../View/?'
|
||||
'file={}&md5={}&type={}">{}'
|
||||
'</a></td><tr>').format(
|
||||
escape(fileparam),
|
||||
md5_hash,
|
||||
typ,
|
||||
escape(fileparam))
|
||||
elif not jfile.endswith('.DS_Store'):
|
||||
typ = 'others'
|
||||
other_files += ('<tr><td><a href="../View/?'
|
||||
'file={}&md5={}&type={}">{}'
|
||||
'</a></td><tr>').format(
|
||||
escape(fileparam),
|
||||
md5_hash,
|
||||
typ,
|
||||
escape(fileparam))
|
||||
except Exception:
|
||||
logger.exception('Dynamic File Analysis')
|
||||
emails.append(email)
|
||||
# Tar dump and fetch files
|
||||
all_files = get_app_files(apk_dir, md5_hash, package)
|
||||
analysis_result['urls'] = urls
|
||||
analysis_result['domains'] = domains
|
||||
analysis_result['emails'] = emails
|
||||
analysis_result['clipboard'] = clipboard
|
||||
analysis_result['web_data'] = web_data
|
||||
analysis_result['xmlfiles'] = xmlfiles
|
||||
analysis_result['sqlite_db'] = sqlite_db
|
||||
analysis_result['other_files'] = other_files
|
||||
analysis_result['xml'] = all_files['xml']
|
||||
analysis_result['sqlite'] = all_files['sqlite']
|
||||
analysis_result['other_files'] = all_files['others']
|
||||
return analysis_result
|
||||
|
||||
|
||||
def download(md5_hash, download_dir, apk_dir, package):
|
||||
def get_screenshots(md5_hash, download_dir):
|
||||
"""Get Screenshots."""
|
||||
# Only After Download Process is Done
|
||||
result = {}
|
||||
imgs = []
|
||||
act_imgs = []
|
||||
expact_imgs = []
|
||||
act = {}
|
||||
exp_act = {}
|
||||
try:
|
||||
screen_dir = os.path.join(download_dir,
|
||||
md5_hash + '-screenshots-apk/')
|
||||
sadb = StaticAnalyzerAndroid.objects.filter(MD5=md5_hash)
|
||||
if os.path.exists(screen_dir) and sadb.exists():
|
||||
for img in os.listdir(screen_dir):
|
||||
if img.endswith('.png'):
|
||||
if img.startswith('act'):
|
||||
act_imgs.append(img)
|
||||
elif img.startswith('expact'):
|
||||
expact_imgs.append(img)
|
||||
else:
|
||||
imgs.append(img)
|
||||
exported_act = python_list(sadb[0].EXPORTED_ACT)
|
||||
act_desc = python_list(sadb[0].ACTIVITIES)
|
||||
if act_imgs:
|
||||
if len(act_imgs) == len(act_desc):
|
||||
act = dict(list(zip(act_imgs, act_desc)))
|
||||
if expact_imgs:
|
||||
if len(expact_imgs) == len(exported_act):
|
||||
exp_act = dict(list(zip(expact_imgs, exported_act)))
|
||||
except Exception:
|
||||
logger.exception('Organising screenshots')
|
||||
result['screenshots'] = imgs
|
||||
result['activities'] = act
|
||||
result['exported_activities'] = exp_act
|
||||
return result
|
||||
|
||||
|
||||
def get_log_data(apk_dir, package):
|
||||
"""Get Data for analysis."""
|
||||
logcat_data = []
|
||||
droidmon_data = ''
|
||||
apimon_data = ''
|
||||
frida_logs = ''
|
||||
web_data = ''
|
||||
traffic = ''
|
||||
httptools = os.path.join(str(Path.home()), '.httptools')
|
||||
web = os.path.join(httptools, 'flows', package + '.flow.txt')
|
||||
logcat = os.path.join(apk_dir, 'logcat.txt')
|
||||
xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
|
||||
apimon = os.path.join(apk_dir, 'mobsf_api_monitor.txt')
|
||||
fd_logs = os.path.join(apk_dir, 'mobsf_frida_out.txt')
|
||||
if is_file_exists(web):
|
||||
with io.open(web,
|
||||
mode='r',
|
||||
encoding='utf8',
|
||||
errors='ignore') as flip:
|
||||
web_data = flip.read()
|
||||
if is_file_exists(logcat):
|
||||
with io.open(logcat,
|
||||
mode='r',
|
||||
encoding='utf8',
|
||||
errors='ignore') as flip:
|
||||
logcat_data = flip.readlines()
|
||||
traffic = ''.join(logcat_data)
|
||||
if is_file_exists(xlogcat):
|
||||
with io.open(xlogcat,
|
||||
mode='r',
|
||||
encoding='utf8',
|
||||
errors='ignore') as flip:
|
||||
droidmon_data = flip.read()
|
||||
if is_file_exists(apimon):
|
||||
with io.open(apimon,
|
||||
mode='r',
|
||||
encoding='utf8',
|
||||
errors='ignore') as flip:
|
||||
apimon_data = flip.read()
|
||||
if is_file_exists(fd_logs):
|
||||
with io.open(apimon,
|
||||
mode='r',
|
||||
encoding='utf8',
|
||||
errors='ignore') as flip:
|
||||
frida_logs = flip.read()
|
||||
traffic = (web_data + traffic + droidmon_data
|
||||
+ apimon_data + frida_logs)
|
||||
return {'logcat': logcat_data,
|
||||
'traffic': traffic}
|
||||
|
||||
|
||||
def get_app_files(apk_dir, md5_hash, package):
|
||||
"""Get files from device."""
|
||||
logger.info('Getting app files')
|
||||
all_files = {'xml': [], 'sqlite': [], 'others': []}
|
||||
# Extract Device Data
|
||||
tar_loc = os.path.join(apk_dir, package + '.tar')
|
||||
untar_dir = os.path.join(apk_dir, 'DYNAMIC_DeviceData/')
|
||||
if not is_file_exists(tar_loc):
|
||||
return all_files
|
||||
if os.path.exists(untar_dir):
|
||||
# fix for permission errors
|
||||
shutil.rmtree(untar_dir)
|
||||
try:
|
||||
with tarfile.open(tar_loc, errorlevel=1) as tar:
|
||||
tar.extractall(untar_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception('Tar extraction failed')
|
||||
# Do Static Analysis on Data from Device
|
||||
try:
|
||||
if not os.path.exists(untar_dir):
|
||||
os.makedirs(untar_dir)
|
||||
for dir_name, _, files in os.walk(untar_dir):
|
||||
for jfile in files:
|
||||
file_path = os.path.join(untar_dir, dir_name, jfile)
|
||||
fileparam = file_path.replace(untar_dir, '')
|
||||
if is_pipe_or_link(file_path):
|
||||
continue
|
||||
if jfile == 'lib':
|
||||
pass
|
||||
else:
|
||||
if jfile.endswith('.xml'):
|
||||
all_files['xml'].append(
|
||||
{'type': 'xml', 'file': fileparam})
|
||||
else:
|
||||
with open(file_path,
|
||||
'r',
|
||||
encoding='ISO-8859-1') as flip:
|
||||
file_cnt_sig = flip.read(6)
|
||||
if file_cnt_sig == 'SQLite':
|
||||
all_files['sqlite'].append(
|
||||
{'type': 'db', 'file': fileparam})
|
||||
elif not jfile.endswith('.DS_Store'):
|
||||
all_files['others'].append(
|
||||
{'type': 'others', 'file': fileparam})
|
||||
except Exception:
|
||||
logger.exception('Getting app files')
|
||||
return all_files
|
||||
|
||||
|
||||
def generate_download(apk_dir, md5_hash, download_dir, package):
|
||||
"""Generating Downloads."""
|
||||
logger.info('Generating Downloads')
|
||||
try:
|
||||
|
||||
capfuzz_home = os.path.join(str(Path.home()), '.capfuzz')
|
||||
httptools = os.path.join(str(Path.home()), '.httptools')
|
||||
logcat = os.path.join(apk_dir, 'logcat.txt')
|
||||
xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
|
||||
apimon = os.path.join(apk_dir, 'mobsf_api_monitor.txt')
|
||||
fd_logs = os.path.join(apk_dir, 'mobsf_frida_out.txt')
|
||||
dumpsys = os.path.join(apk_dir, 'dump.txt')
|
||||
sshot = os.path.join(apk_dir, 'screenshots-apk/')
|
||||
web = os.path.join(capfuzz_home, 'flows', package + '.flows.txt')
|
||||
web = os.path.join(httptools, 'flows', package + '.flow.txt')
|
||||
star = os.path.join(apk_dir, package + '.tar')
|
||||
|
||||
dlogcat = os.path.join(download_dir, md5_hash + '-logcat.txt')
|
||||
dxlogcat = os.path.join(download_dir, md5_hash + '-x_logcat.txt')
|
||||
dapimon = os.path.join(download_dir, md5_hash + '-api_monitor.txt')
|
||||
dfd_logs = os.path.join(download_dir, md5_hash + '-frida_out.txt')
|
||||
ddumpsys = os.path.join(download_dir, md5_hash + '-dump.txt')
|
||||
dsshot = os.path.join(download_dir, md5_hash + '-screenshots-apk/')
|
||||
dweb = os.path.join(download_dir, md5_hash + '-WebTraffic.txt')
|
||||
dstar = os.path.join(download_dir, md5_hash + '-AppData.tar')
|
||||
dweb = os.path.join(download_dir, md5_hash + '-web_traffic.txt')
|
||||
dstar = os.path.join(download_dir, md5_hash + '-app_data.tar')
|
||||
|
||||
# Delete existing data
|
||||
dellist = [dlogcat, dxlogcat, ddumpsys, dsshot, dweb, dstar]
|
||||
dellist = [dlogcat, dxlogcat, dapimon,
|
||||
dfd_logs, ddumpsys, dsshot,
|
||||
dweb, dstar]
|
||||
for item in dellist:
|
||||
if os.path.isdir(item):
|
||||
shutil.rmtree(item)
|
||||
@@ -300,19 +242,20 @@ def download(md5_hash, download_dir, apk_dir, package):
|
||||
os.remove(item)
|
||||
# Copy new data
|
||||
shutil.copyfile(logcat, dlogcat)
|
||||
shutil.copyfile(xlogcat, dxlogcat)
|
||||
shutil.copyfile(dumpsys, ddumpsys)
|
||||
if is_file_exists(xlogcat):
|
||||
shutil.copyfile(xlogcat, dxlogcat)
|
||||
if is_file_exists(apimon):
|
||||
shutil.copyfile(apimon, dapimon)
|
||||
if is_file_exists(fd_logs):
|
||||
shutil.copyfile(fd_logs, dfd_logs)
|
||||
try:
|
||||
shutil.copytree(sshot, dsshot)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if is_file_exists(web):
|
||||
shutil.copyfile(web, dweb)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if is_file_exists(star):
|
||||
shutil.copyfile(star, dstar)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception('Generating Downloads')
|
||||
|
||||
@@ -1,945 +0,0 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Core Functions of Android Dynamic Analysis."""
|
||||
import json
|
||||
import logging
|
||||
import ntpath
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import sqlite3 as sq
|
||||
import threading
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.template.defaulttags import register
|
||||
from django.utils.html import escape
|
||||
|
||||
from DynamicAnalyzer.tools.webproxy import (get_ca_dir, start_fuzz_ui,
|
||||
start_proxy, stop_capfuzz)
|
||||
from DynamicAnalyzer.views.android.analysis import (api_analysis, download,
|
||||
run_analysis)
|
||||
from DynamicAnalyzer.views.android.shared import (adb_command, connect,
|
||||
get_identifier, get_res,
|
||||
install_and_run, wait)
|
||||
from DynamicAnalyzer.views.android.virtualbox_vm import refresh_vm
|
||||
|
||||
from MobSF.utils import (get_adb, is_number,
|
||||
print_n_send_error_response,
|
||||
python_list)
|
||||
|
||||
from StaticAnalyzer.models import StaticAnalyzerAndroid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ===========================================
|
||||
# Dynamic Analyzer Related Views for Android
|
||||
# ===========================================
|
||||
|
||||
|
||||
"""
|
||||
Need to improve RCE Detection on Framework, audit all subprocess calls
|
||||
TCP Connnection to screenshot service needs to be secured.
|
||||
Globals!
|
||||
"""
|
||||
|
||||
TCP_SERVER_MODE = 'off' # ScreenCast TCP Service Status
|
||||
|
||||
|
||||
@register.filter
|
||||
def key(d, key_name):
|
||||
"""To get dict element by key name in template."""
|
||||
return d.get(key_name)
|
||||
|
||||
|
||||
def android_dynamic_analyzer(request):
|
||||
"""Android Dynamic Analyzer View."""
|
||||
logger.info('Dynamic Analysis Started')
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
md5_hash = request.POST['md5']
|
||||
package = request.POST['pkg']
|
||||
launcher = request.POST['lng']
|
||||
if (re.findall(r';|\$\(|\|\||&&', package)
|
||||
or re.findall(r';|\$\(|\|\||&&', launcher)):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack')
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
# Delete ScreenCast Cache
|
||||
screen_file = os.path.join(settings.SCREEN_DIR, 'screen.png')
|
||||
if os.path.exists(screen_file):
|
||||
os.remove(screen_file)
|
||||
# Delete Contents of Screenshot Dir
|
||||
screen_dir = os.path.join(
|
||||
settings.UPLD_DIR, md5_hash + '/screenshots-apk/')
|
||||
if os.path.isdir(screen_dir):
|
||||
shutil.rmtree(screen_dir)
|
||||
else:
|
||||
os.makedirs(screen_dir)
|
||||
# Start DM
|
||||
stop_capfuzz(settings.PORT)
|
||||
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
|
||||
logger.info(
|
||||
'MobSF Dynamic Analysis on Real Android Device')
|
||||
else:
|
||||
# Refersh VM
|
||||
refresh_vm(settings.UUID, settings.SUUID, settings.VBOX)
|
||||
context = {'md5': md5_hash,
|
||||
'pkg': package,
|
||||
'lng': launcher,
|
||||
'title': 'Start Testing'}
|
||||
template = 'dynamic_analysis/start_test.html'
|
||||
return render(request, template, context)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed')
|
||||
except Exception:
|
||||
logger.exception('DynamicAnalyzer')
|
||||
return print_n_send_error_response(request,
|
||||
'Dynamic Analysis Failed.')
|
||||
# AJAX
|
||||
|
||||
|
||||
def get_env(request):
|
||||
"""Get Dynamic Analysis Environment for Android."""
|
||||
logger.info('Setting up Dynamic Analysis Environment')
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
data = {}
|
||||
md5_hash = request.POST['md5']
|
||||
package = request.POST['pkg']
|
||||
launcher = request.POST['lng']
|
||||
if (re.findall(r';|\$\(|\|\||&&', package)
|
||||
or re.findall(r';|\$\(|\|\||&&', launcher)):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack',
|
||||
True)
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
app_dir = os.path.join(
|
||||
settings.UPLD_DIR, md5_hash + '/') # APP DIRECTORY
|
||||
app_file = md5_hash + '.apk' # NEW FILENAME
|
||||
app_path = app_dir + app_file # APP PATH
|
||||
start_proxy(settings.PORT, package)
|
||||
# vm needs the connect function
|
||||
try:
|
||||
connect()
|
||||
except Exception as exp:
|
||||
data = {'ready': 'no',
|
||||
'msg': 'Cannot Connect to the VM/Device.',
|
||||
'error': str(exp)}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
# Change True to support non-activity components
|
||||
install_and_run(app_path, package, launcher, True)
|
||||
screen_width, screen_width = get_res()
|
||||
data = {'ready': 'yes',
|
||||
'screen_witdth': screen_width,
|
||||
'screen_height': screen_width}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash',
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
except Exception:
|
||||
logger.exception('Setting up Dynamic Analysis Environment')
|
||||
return print_n_send_error_response(request,
|
||||
'Environment Setup Failed',
|
||||
True)
|
||||
# AJAX
|
||||
|
||||
|
||||
def take_screenshot(request):
|
||||
"""Take Screenshot."""
|
||||
logger.info('Taking Screenshot')
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
md5_hash = request.POST['md5']
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
data = {}
|
||||
rand_int = random.randint(1, 1000000)
|
||||
# make sure that list only png from this directory
|
||||
screen_dir = os.path.join(
|
||||
settings.UPLD_DIR, md5_hash + '/screenshots-apk/')
|
||||
if not os.path.exists(screen_dir):
|
||||
os.makedirs(screen_dir)
|
||||
adb_command(
|
||||
['screencap', '-p', '/data/local/screen.png'], True)
|
||||
adb_command(['pull',
|
||||
'/data/local/screen.png',
|
||||
'{}screenshot-{}.png'.format(
|
||||
screen_dir,
|
||||
str(rand_int))])
|
||||
logger.info('Screenshot Taken')
|
||||
data = {'screenshot': 'yes'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash',
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
except Exception:
|
||||
logger.exception('Taking Screenshot')
|
||||
return print_n_send_error_response(request,
|
||||
'Error Taking Screenshot',
|
||||
True)
|
||||
# AJAX
|
||||
|
||||
|
||||
def screen_cast(request):
|
||||
"""Start or Stop ScreenCast Feature."""
|
||||
logger.info('Invoking ScreenCast Service in VM/Device')
|
||||
try:
|
||||
global TCP_SERVER_MODE
|
||||
data = {}
|
||||
if request.method == 'POST':
|
||||
mode = request.POST['mode']
|
||||
ip_address = settings.SCREEN_IP
|
||||
port = str(settings.SCREEN_PORT)
|
||||
if mode == 'on':
|
||||
args = ['am',
|
||||
'startservice',
|
||||
'-a',
|
||||
ip_address + ':' + port,
|
||||
'opensecurity.screencast/.StartScreenCast']
|
||||
data = {'status': 'on'}
|
||||
TCP_SERVER_MODE = 'on'
|
||||
elif mode == 'off':
|
||||
args = ['am',
|
||||
'force-stop',
|
||||
'opensecurity.screencast']
|
||||
data = {'status': 'off'}
|
||||
TCP_SERVER_MODE = 'off'
|
||||
if (mode in ['on', 'off']):
|
||||
try:
|
||||
adb_command(args, True)
|
||||
screen_trd = threading.Thread(target=screencast_service)
|
||||
screen_trd.setDaemon(True)
|
||||
screen_trd.start()
|
||||
except Exception:
|
||||
logger.exception('Casting Screen')
|
||||
data = {'status': 'error'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
data = {'status': 'failed'}
|
||||
else:
|
||||
data = {'status': 'failed'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
except Exception:
|
||||
logger.exception('Casting Screen')
|
||||
return print_n_send_error_response(request,
|
||||
'Error Casting Screen',
|
||||
True)
|
||||
# AJAX
|
||||
|
||||
|
||||
def clip_dump(request):
|
||||
"""Dump Android Clipboard."""
|
||||
logger.info('Starting Clipboard Dump Service in VM/Device')
|
||||
try:
|
||||
data = {}
|
||||
if request.method == 'POST':
|
||||
args = ['am',
|
||||
'startservice',
|
||||
'opensecurity.clipdump/.ClipDumper']
|
||||
try:
|
||||
adb_command(args, True)
|
||||
data = {'status': 'success'}
|
||||
except Exception:
|
||||
logger.exception('Dumping Clipboard')
|
||||
data = {'status': 'error'}
|
||||
else:
|
||||
data = {'status': 'failed'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
except Exception:
|
||||
logger.exception('Dumping Clipboard')
|
||||
return print_n_send_error_response(request,
|
||||
'Error Dumping Clipboard',
|
||||
True)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
def touch(request):
|
||||
"""Sending Touch Events."""
|
||||
logger.info('Sending Touch Events')
|
||||
try:
|
||||
data = {}
|
||||
if (request.method == 'POST'
|
||||
and is_number(request.POST['x'])
|
||||
and is_number(request.POST['y'])):
|
||||
x_axis = request.POST['x']
|
||||
y_axis = request.POST['y']
|
||||
args = ['input',
|
||||
'tap',
|
||||
x_axis,
|
||||
y_axis]
|
||||
data = {'status': 'success'}
|
||||
try:
|
||||
adb_command(args, True)
|
||||
except Exception:
|
||||
data = {'status': 'error'}
|
||||
logger.exception('Performing Touch Action')
|
||||
else:
|
||||
data = {'status': 'failed'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
except Exception:
|
||||
logger.exception('Sending Touch Events')
|
||||
return print_n_send_error_response(request,
|
||||
'Error Sending Touch Events',
|
||||
True)
|
||||
# AJAX
|
||||
|
||||
|
||||
def execute_adb(request):
|
||||
"""Execute ADB Commands."""
|
||||
logger.info('Executing ADB Commands')
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
data = {}
|
||||
cmd = request.POST['cmd']
|
||||
resp = 'error'
|
||||
try:
|
||||
resp = adb_command(cmd.split(' '))
|
||||
except Exception:
|
||||
logger.exception('Executing ADB Commands')
|
||||
data = {'cmd': 'yes', 'resp': resp.decode('utf8', 'ignore')}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
except Exception:
|
||||
logger.exception('Executing ADB Commands')
|
||||
return print_n_send_error_response(request,
|
||||
'Error running ADB commands',
|
||||
True)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
def mobsf_ca(request):
|
||||
"""Install and Remove MobSF Proxy RootCA."""
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
data = {}
|
||||
act = request.POST['action']
|
||||
rootca = get_ca_dir()
|
||||
if act == 'install':
|
||||
logger.info('Installing MobSF RootCA')
|
||||
adb_command(
|
||||
['push', rootca, '/data/local/tmp/' + settings.ROOT_CA])
|
||||
ca_file = '/system/etc/security/cacerts/' + settings.ROOT_CA
|
||||
adb_command(['su',
|
||||
'-c',
|
||||
'cp',
|
||||
'/data/local/tmp/' + settings.ROOT_CA,
|
||||
ca_file], True)
|
||||
adb_command(['su',
|
||||
'-c',
|
||||
'chmod',
|
||||
'644',
|
||||
ca_file], True)
|
||||
adb_command(['rm',
|
||||
'/data/local/tmp/' + settings.ROOT_CA], True)
|
||||
data = {'ca': 'installed'}
|
||||
elif act == 'remove':
|
||||
logger.info('Removing MobSF RootCA')
|
||||
adb_command(
|
||||
['su',
|
||||
'-c',
|
||||
'rm',
|
||||
ca_file], True)
|
||||
data = {'ca': 'removed'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
except Exception:
|
||||
logger.exception('MobSF RootCA Handler')
|
||||
return print_n_send_error_response(request,
|
||||
'Error in RootCA Handler',
|
||||
True)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
def final_test(request):
|
||||
"""Collecting Data and Cleanup."""
|
||||
global TCP_SERVER_MODE
|
||||
logger.info('Collecting Data and Cleaning Up')
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
data = {}
|
||||
md5_hash = request.POST['md5']
|
||||
package = request.POST['pkg']
|
||||
if re.findall(r';|\$\(|\|\||&&', package):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack',
|
||||
True)
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
# Stop ScreenCast Client if it is running
|
||||
TCP_SERVER_MODE = 'off'
|
||||
apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
adb = get_adb()
|
||||
# Change to check output of subprocess when analysis is done
|
||||
# Can't RCE
|
||||
cmd = ('{} -s {} logcat -d dalvikvm:'
|
||||
'W ActivityManager:I > "{}logcat.txt"').format(
|
||||
adb,
|
||||
get_identifier(),
|
||||
apk_dir)
|
||||
os.system(cmd)
|
||||
logger.info('Downloading Logcat logs')
|
||||
xposed_out = ('/data/data/'
|
||||
'de.robv.android.xposed.installer'
|
||||
'/log/error.log')
|
||||
adb_command(['pull',
|
||||
xposed_out,
|
||||
apk_dir + 'x_logcat.txt'])
|
||||
logger.info('Downloading Droidmon API Monitor Logcat logs')
|
||||
# Can't RCE
|
||||
cmd = '{} -s {} shell dumpsys > "{}dump.txt"'.format(
|
||||
adb,
|
||||
get_identifier(),
|
||||
apk_dir)
|
||||
os.system(cmd)
|
||||
logger.info('Downloading Dumpsys logs')
|
||||
|
||||
adb_command(['am', 'force-stop', package], True)
|
||||
logger.info('Stopping Application')
|
||||
|
||||
adb_command(
|
||||
['am', 'force-stop', 'opensecurity.screencast'], True)
|
||||
logger.info('Stopping ScreenCast Service')
|
||||
|
||||
data = {'final': 'yes'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash',
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
except Exception:
|
||||
err = 'Data Collection & Clean Up failed'
|
||||
logger.exception(err)
|
||||
return print_n_send_error_response(request,
|
||||
err,
|
||||
True)
|
||||
# AJAX
|
||||
|
||||
|
||||
def dump_data(request):
|
||||
"""Downloading Application Data from Device."""
|
||||
logger.info('Downloading Application Data from Device')
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
data = {}
|
||||
package = request.POST['pkg']
|
||||
md5_hash = request.POST['md5']
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
if re.findall(r';|\$\(|\|\||&&', package):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack',
|
||||
True)
|
||||
apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
# Let's try to close Proxy a bit early as we don't have much
|
||||
# control on the order of thread execution
|
||||
stop_capfuzz(settings.PORT)
|
||||
logger.info('Deleting Dump Status File')
|
||||
adb_command(['rm', '/sdcard/mobsec_status'], True)
|
||||
logger.info('Creating TAR of Application Files.')
|
||||
dp = 'opensecurity.ajin.datapusher/.GetPackageLocation'
|
||||
adb_command(['am',
|
||||
'startservice',
|
||||
'-a',
|
||||
package,
|
||||
dp], True)
|
||||
logger.info('Waiting for TAR dump to complete...')
|
||||
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
|
||||
timeout = settings.DEVICE_TIMEOUT
|
||||
else:
|
||||
timeout = settings.VM_TIMEOUT
|
||||
start_time = time.time()
|
||||
while True:
|
||||
current_time = time.time()
|
||||
out = adb_command(
|
||||
['cat', '/sdcard/mobsec_status'], shell=True)
|
||||
if b'MOBSEC-TAR-CREATED' in out:
|
||||
break
|
||||
if (current_time - start_time) > timeout:
|
||||
logger.error(
|
||||
'TAR Generation Failed. Process timed out.')
|
||||
break
|
||||
logger.info('Dumping Application Files from Device/VM')
|
||||
adb_command(['pull',
|
||||
'/data/local/' + package + '.tar',
|
||||
apk_dir + package + '.tar'])
|
||||
logger.info('Stopping ADB')
|
||||
adb_command(['kill-server'])
|
||||
data = {'dump': 'yes'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash',
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
except Exception:
|
||||
logger.exception('Downloading Application Data from Device')
|
||||
err = 'Application Data Dump from Device failed'
|
||||
return print_n_send_error_response(request,
|
||||
err,
|
||||
True)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
def exported_activity_tester(request):
|
||||
"""Exported Activity Tester."""
|
||||
logger.info('Exported Activity Tester')
|
||||
try:
|
||||
md5_hash = request.POST['md5']
|
||||
package = request.POST['pkg']
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
if re.findall(r';|\$\(|\|\||&&', package):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack',
|
||||
True)
|
||||
if request.method == 'POST':
|
||||
app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
screen_dir = os.path.join(app_dir, 'screenshots-apk/')
|
||||
if not os.path.exists(screen_dir):
|
||||
os.makedirs(screen_dir)
|
||||
data = {}
|
||||
static_android_db = StaticAnalyzerAndroid.objects.filter(
|
||||
MD5=md5_hash)
|
||||
if static_android_db.exists():
|
||||
logger.info('Fetching Exported Activity List from DB')
|
||||
exported_act = python_list(
|
||||
static_android_db[0].EXPORTED_ACT)
|
||||
if exported_act:
|
||||
exp_act_no = 0
|
||||
logger.info('Starting Exported Activity Tester...')
|
||||
logger.info('%s Exported Activities Identified',
|
||||
str(len(exported_act)))
|
||||
for line in exported_act:
|
||||
try:
|
||||
exp_act_no += 1
|
||||
expa = 'Launching Exported Activity'
|
||||
logger.info('%s - %s. %s',
|
||||
expa,
|
||||
str(exp_act_no),
|
||||
line)
|
||||
adb_command(['am',
|
||||
'start',
|
||||
'-n',
|
||||
package + '/' + line], True)
|
||||
wait(4)
|
||||
adb_command(['screencap',
|
||||
'-p',
|
||||
'/data/local/screen.png'], True)
|
||||
# ? get appended from Air :-()
|
||||
# if activity names are used
|
||||
outfile = '{}expact-{}.png'.format(
|
||||
screen_dir, str(exp_act_no))
|
||||
adb_command(['pull',
|
||||
'/data/local/screen.png',
|
||||
outfile])
|
||||
logger.info('Activity Screenshot Taken')
|
||||
adb_command(['am',
|
||||
'force-stop',
|
||||
package], True)
|
||||
logger.info('Stopping App')
|
||||
except Exception:
|
||||
logger.exception('Exported Activity Tester')
|
||||
data = {'expacttest': 'done'}
|
||||
else:
|
||||
logger.info(
|
||||
'Exported Activity Tester - No Activity Found!')
|
||||
data = {'expacttest': 'noact'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
err = 'Entry does not exist in DB'
|
||||
return print_n_send_error_response(request,
|
||||
err,
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash',
|
||||
True)
|
||||
except Exception:
|
||||
err = 'Error Running Exported Activity Tests'
|
||||
logger.exception('ERROR] Exported Activity Tester')
|
||||
return print_n_send_error_response(request,
|
||||
err,
|
||||
True)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
def activity_tester(request):
|
||||
"""Activity Tester."""
|
||||
logger.info('Activity Tester')
|
||||
try:
|
||||
md5_hash = request.POST['md5']
|
||||
package = request.POST['pkg']
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
if re.findall(r';|\$\(|\|\||&&', package):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack',
|
||||
True)
|
||||
if request.method == 'POST':
|
||||
app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
screen_dir = os.path.join(app_dir, 'screenshots-apk/')
|
||||
if not os.path.exists(screen_dir):
|
||||
os.makedirs(screen_dir)
|
||||
data = {}
|
||||
static_android_db = StaticAnalyzerAndroid.objects.filter(
|
||||
MD5=md5_hash)
|
||||
if static_android_db.exists():
|
||||
logger.info('Fetching Activity List from DB')
|
||||
activities = python_list(static_android_db[0].ACTIVITIES)
|
||||
if activities:
|
||||
act_no = 0
|
||||
logger.info('Starting Activity Tester...')
|
||||
logger.info('%s Activities Identified',
|
||||
str(len(activities)))
|
||||
for line in activities:
|
||||
try:
|
||||
act_no += 1
|
||||
logger.info(
|
||||
'Launching Activity - %s. %s',
|
||||
str(act_no),
|
||||
line)
|
||||
adb_command(['am',
|
||||
'start',
|
||||
'-n',
|
||||
package + '/' + line], True)
|
||||
wait(4)
|
||||
adb_command(
|
||||
['screencap',
|
||||
'-p',
|
||||
'/data/local/screen.png'], True)
|
||||
# ? get appended from Air :-()
|
||||
# if activity names are used
|
||||
outfile = ('{}act-{}.png'.format(
|
||||
screen_dir,
|
||||
act_no))
|
||||
adb_command(['pull',
|
||||
'/data/local/screen.png',
|
||||
outfile])
|
||||
logger.info('Activity Screenshot Taken')
|
||||
adb_command(
|
||||
['am', 'force-stop', package], True)
|
||||
logger.info('Stopping App')
|
||||
except Exception:
|
||||
logger.exception('Activity Tester')
|
||||
data = {'acttest': 'done'}
|
||||
else:
|
||||
logger.info('Activity Tester - No Activity Found!')
|
||||
data = {'acttest': 'noact'}
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
else:
|
||||
err = 'Entry does not exist in DB'
|
||||
return print_n_send_error_response(request,
|
||||
err,
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only POST allowed',
|
||||
True)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash',
|
||||
True)
|
||||
except Exception:
|
||||
logger.exception('Activity Tester')
|
||||
return print_n_send_error_response(request,
|
||||
'Error Running Activity Tester',
|
||||
True)
|
||||
|
||||
|
||||
def report(request):
|
||||
"""Dynamic Analysis Report Generation."""
|
||||
logger.info('Dynamic Analysis Report Generation')
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
md5_hash = request.GET['md5']
|
||||
package = request.GET['pkg']
|
||||
if re.findall(r';|\$\(|\|\||&&', package):
|
||||
return print_n_send_error_response(request,
|
||||
'Possible RCE Attack')
|
||||
if re.match('^[0-9a-f]{32}$', md5_hash):
|
||||
app_dir = os.path.join(
|
||||
settings.UPLD_DIR, md5_hash + '/') # APP DIRECTORY
|
||||
download_dir = settings.DWD_DIR
|
||||
droidmon_api_loc = os.path.join(app_dir, 'x_logcat.txt')
|
||||
api_analysis_result = api_analysis(package, droidmon_api_loc)
|
||||
analysis_result = run_analysis(app_dir, md5_hash, package)
|
||||
download(md5_hash, download_dir, app_dir, package)
|
||||
# Only After Download Process is Done
|
||||
imgs = []
|
||||
act_imgs = []
|
||||
act = {}
|
||||
expact_imgs = []
|
||||
exp_act = {}
|
||||
if os.path.exists(os.path.join(
|
||||
download_dir,
|
||||
md5_hash + '-screenshots-apk/')):
|
||||
try:
|
||||
imp_path = os.path.join(
|
||||
download_dir, md5_hash + '-screenshots-apk/')
|
||||
for img in os.listdir(imp_path):
|
||||
if img.endswith('.png'):
|
||||
if img.startswith('act'):
|
||||
act_imgs.append(img)
|
||||
elif img.startswith('expact'):
|
||||
expact_imgs.append(img)
|
||||
else:
|
||||
imgs.append(img)
|
||||
sadb = StaticAnalyzerAndroid.objects.filter(
|
||||
MD5=md5_hash)
|
||||
if sadb.exists():
|
||||
logger.info(
|
||||
'\nFetching Exported Activity'
|
||||
' & Activity List from DB')
|
||||
exported_act = python_list(
|
||||
sadb[0].EXPORTED_ACT)
|
||||
act_desc = python_list(
|
||||
sadb[0].ACTIVITIES)
|
||||
if act_imgs:
|
||||
if len(act_imgs) == len(act_desc):
|
||||
act = dict(list(zip(act_imgs, act_desc)))
|
||||
if expact_imgs:
|
||||
if len(expact_imgs) == len(exported_act):
|
||||
exp_act = dict(
|
||||
list(zip(expact_imgs, exported_act)))
|
||||
else:
|
||||
logger.warning('Entry does not exists in the DB.')
|
||||
except Exception:
|
||||
logger.exception('Screenshot Sorting')
|
||||
context = {'md5': md5_hash,
|
||||
'emails': analysis_result['emails'],
|
||||
'urls': analysis_result['urls'],
|
||||
'domains': analysis_result['domains'],
|
||||
'clipboard': analysis_result['clipboard'],
|
||||
'http': analysis_result['web_data'],
|
||||
'xml': analysis_result['xmlfiles'],
|
||||
'sqlite': analysis_result['sqlite_db'],
|
||||
'others': analysis_result['other_files'],
|
||||
'imgs': imgs,
|
||||
'acttest': act,
|
||||
'expacttest': exp_act,
|
||||
'net': api_analysis_result['api_net'],
|
||||
'base64': api_analysis_result['api_base64'],
|
||||
'crypto': api_analysis_result['api_crypto'],
|
||||
'fileio': api_analysis_result['api_fileio'],
|
||||
'binder': api_analysis_result['api_binder'],
|
||||
'divinfo': api_analysis_result['api_deviceinfo'],
|
||||
'cntval': api_analysis_result['api_cntvl'],
|
||||
'sms': api_analysis_result['api_sms'],
|
||||
'sysprop': api_analysis_result['api_sysprop'],
|
||||
'dexload': api_analysis_result['api_dexloader'],
|
||||
'reflect': api_analysis_result['api_reflect'],
|
||||
'sysman': api_analysis_result['api_acntmnger'],
|
||||
'process': api_analysis_result['api_cmd'],
|
||||
'pkg': package,
|
||||
'title': 'Dynamic Analysis'}
|
||||
template = 'dynamic_analysis/dynamic_analysis.html'
|
||||
return render(request, template, context)
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Scan Hash')
|
||||
else:
|
||||
return print_n_send_error_response(request,
|
||||
'Only GET allowed')
|
||||
except Exception:
|
||||
logger.exception('Dynamic Analysis Report Generation')
|
||||
err = 'Error Geneating Dynamic Analysis Report'
|
||||
return print_n_send_error_response(request, err)
|
||||
|
||||
|
||||
def handle_sqlite(sfile):
|
||||
"""Sqlite Dump - Readable Text."""
|
||||
logger.info('SQLite DB Extraction')
|
||||
try:
|
||||
data = ''
|
||||
con = sq.connect(sfile)
|
||||
cur = con.cursor()
|
||||
cur.execute('SELECT name FROM sqlite_master WHERE type=\'table\';')
|
||||
tables = cur.fetchall()
|
||||
for table in tables:
|
||||
data += ('\nTABLE: {}'
|
||||
' \n==============='
|
||||
'==================='
|
||||
'===================\n'.format(table[0]))
|
||||
cur.execute('PRAGMA table_info(\'%s\')' % table)
|
||||
rows = cur.fetchall()
|
||||
head = ''
|
||||
for sq_row in rows:
|
||||
elm_data = sq_row[1]
|
||||
head += elm_data + ' | '
|
||||
data += ('{} \n================'
|
||||
'============================'
|
||||
'=========================\n'.format(head))
|
||||
cur.execute('SELECT * FROM \'%s\'' % table)
|
||||
rows = cur.fetchall()
|
||||
for sq_row in rows:
|
||||
dat = ''
|
||||
for each_row in sq_row:
|
||||
dat += str(each_row) + ' | '
|
||||
data += dat + '\n'
|
||||
return data
|
||||
except Exception:
|
||||
logger.exception('SQLite DB Extraction')
|
||||
|
||||
|
||||
def view(request):
|
||||
"""View File."""
|
||||
logger.info('Viewing File')
|
||||
try:
|
||||
typ = ''
|
||||
fil = ''
|
||||
rtyp = ''
|
||||
dat = ''
|
||||
if re.match('^[0-9a-f]{32}$', request.GET['md5']):
|
||||
fil = request.GET['file']
|
||||
md5_hash = request.GET['md5']
|
||||
typ = request.GET['type']
|
||||
src = os.path.join(
|
||||
settings.UPLD_DIR,
|
||||
md5_hash + '/DYNAMIC_DeviceData/')
|
||||
sfile = os.path.join(src, fil)
|
||||
# Prevent Directory Traversal Attacks
|
||||
if (('../' in fil)
|
||||
or ('%2e%2e' in fil)
|
||||
or ('..' in fil)
|
||||
or ('%252e' in fil)):
|
||||
err = 'Path Traversal Attack Detected'
|
||||
return print_n_send_error_response(request, err)
|
||||
else:
|
||||
with open(sfile, 'r') as flip:
|
||||
dat = flip.read()
|
||||
if (fil.endswith('.xml')) and (typ == 'xml'):
|
||||
rtyp = 'xml'
|
||||
elif typ == 'db':
|
||||
dat = handle_sqlite(sfile)
|
||||
rtyp = 'asciidoc'
|
||||
elif typ == 'others':
|
||||
rtyp = 'asciidoc'
|
||||
else:
|
||||
err = 'File Type not supported'
|
||||
return print_n_send_error_response(request, err)
|
||||
context = {'title': escape(ntpath.basename(fil)),
|
||||
'file': escape(ntpath.basename(fil)),
|
||||
'dat': dat,
|
||||
'type': rtyp}
|
||||
template = 'general/view.html'
|
||||
return render(request, template, context)
|
||||
else:
|
||||
return print_n_send_error_response(request, 'Invalid Scan Hash')
|
||||
except Exception:
|
||||
logger.exception('Viewing File')
|
||||
return print_n_send_error_response(request, 'ERROR Viewing File')
|
||||
|
||||
|
||||
def capfuzz_start(request):
|
||||
"""Start CapFuzz UI."""
|
||||
logger.info('Starting CapFuzz Web UI')
|
||||
try:
|
||||
stop_capfuzz(settings.PORT)
|
||||
start_fuzz_ui(settings.PORT)
|
||||
time.sleep(3)
|
||||
logger.info('CapFuzz UI Started')
|
||||
if request.GET['project']:
|
||||
project = request.GET['project']
|
||||
else:
|
||||
project = ''
|
||||
url = ('http://localhost:{}'
|
||||
'/dashboard/{}'.format(
|
||||
str(settings.PORT),
|
||||
project))
|
||||
return HttpResponseRedirect(url)
|
||||
except Exception:
|
||||
logger.exception('Starting CapFuzz Web UI')
|
||||
err = 'Error Starting CapFuzz UI'
|
||||
return print_n_send_error_response(request, err)
|
||||
|
||||
|
||||
def screencast_service():
|
||||
"""Start or Stop ScreenCast Services."""
|
||||
global TCP_SERVER_MODE
|
||||
logger.info('ScreenCast Service Status: %s', TCP_SERVER_MODE)
|
||||
try:
|
||||
screen_dir = settings.SCREEN_DIR
|
||||
if not os.path.exists(screen_dir):
|
||||
os.makedirs(screen_dir)
|
||||
|
||||
screen_socket = socket.socket()
|
||||
if TCP_SERVER_MODE == 'on':
|
||||
screen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
addr = (settings.SCREEN_IP, settings.SCREEN_PORT)
|
||||
screen_socket.bind(addr)
|
||||
screen_socket.listen(10)
|
||||
while TCP_SERVER_MODE == 'on':
|
||||
screens, address = screen_socket.accept()
|
||||
logger.info('Got Connection from: %s', address[0])
|
||||
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
|
||||
ip_address = settings.DEVICE_IP
|
||||
else:
|
||||
ip_address = settings.VM_IP
|
||||
if address[0] in [ip_address, '127.0.0.1']:
|
||||
# Very Basic Check to ensure
|
||||
# that only MobSF VM/Device/Emulator
|
||||
# is allowed to connect to MobSF ScreenCast Service.
|
||||
with open(screen_dir + 'screen.png', 'wb') as flip:
|
||||
while True:
|
||||
data = screens.recv(1024)
|
||||
if not data:
|
||||
break
|
||||
flip.write(data)
|
||||
else:
|
||||
logger.warning('\n[ATTACK] An unknown client'
|
||||
' :%s is trying '
|
||||
'to make a connection with '
|
||||
'MobSF ScreenCast Service!', address[0])
|
||||
elif TCP_SERVER_MODE == 'off':
|
||||
screen_socket.close()
|
||||
except Exception:
|
||||
screen_socket.close()
|
||||
logger.exception('ScreenCast Server')
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Android Dynamic Analysis."""
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
from shelljob import proc
|
||||
|
||||
from django.http import (HttpResponseRedirect,
|
||||
StreamingHttpResponse)
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
|
||||
from DynamicAnalyzer.views.android.environment import Environment
|
||||
from DynamicAnalyzer.views.android.operations import (
|
||||
is_attack_pattern,
|
||||
is_md5,
|
||||
strict_package_check)
|
||||
from DynamicAnalyzer.tools.webproxy import (
|
||||
start_httptools_ui,
|
||||
stop_httptools)
|
||||
|
||||
from MobSF.utils import (get_device,
|
||||
get_proxy_ip,
|
||||
print_n_send_error_response)
|
||||
|
||||
|
||||
from StaticAnalyzer.models import StaticAnalyzerAndroid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def dynamic_analysis(request):
|
||||
"""Android Dynamic Analysis Entry point."""
|
||||
try:
|
||||
apks = StaticAnalyzerAndroid.objects.filter(
|
||||
ZIPPED='&type=apk').order_by('-id')
|
||||
try:
|
||||
identifier = get_device()
|
||||
except Exception:
|
||||
msg = ('Is Genymotion Andoird VM running? MobSF cannot'
|
||||
' find android instance identifier.'
|
||||
' Please run an android instance and refresh'
|
||||
' this page. If this error persists,'
|
||||
' set ANALYZER_IDENTIFIER in MobSF/settings.py')
|
||||
return print_n_send_error_response(request, msg)
|
||||
proxy_ip = get_proxy_ip(identifier)
|
||||
context = {'apks': apks,
|
||||
'identifier': identifier,
|
||||
'proxy_ip': proxy_ip,
|
||||
'proxy_port': settings.PROXY_PORT,
|
||||
'title': 'MobSF Dynamic Analysis'}
|
||||
template = 'dynamic_analysis/dynamic_analysis.html'
|
||||
return render(request, template, context)
|
||||
except Exception as exp:
|
||||
logger.exception('Dynamic Analysis')
|
||||
return print_n_send_error_response(request,
|
||||
exp)
|
||||
|
||||
|
||||
def dynamic_analyzer(request):
|
||||
"""Android Dynamic Analyzer Environment."""
|
||||
logger.info('Creating Dynamic Analysis Environment')
|
||||
try:
|
||||
bin_hash = request.GET['hash']
|
||||
package = request.GET['package']
|
||||
no_device = False
|
||||
if (is_attack_pattern(package)
|
||||
or not is_md5(bin_hash)):
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Parameters')
|
||||
try:
|
||||
identifier = get_device()
|
||||
except Exception:
|
||||
no_device = True
|
||||
if no_device or not identifier:
|
||||
msg = ('Is the android instance running? MobSF cannot'
|
||||
' find android instance identifier. '
|
||||
'Please run an android instance and refresh'
|
||||
' this page. If this error persists,'
|
||||
' set ANALYZER_IDENTIFIER in MobSF/settings.py')
|
||||
return print_n_send_error_response(request, msg)
|
||||
env = Environment(identifier)
|
||||
if not env.connect_n_mount():
|
||||
msg = 'Cannot Connect to ' + identifier
|
||||
return print_n_send_error_response(request, msg)
|
||||
version = env.get_android_version()
|
||||
logger.info('Android Version identified as %s', version)
|
||||
xposed_first_run = False
|
||||
if not env.is_mobsfyied(version):
|
||||
msg = ('This Android instance is not MobSfyed.\n'
|
||||
'MobSFying the android runtime environment')
|
||||
logger.warning(msg)
|
||||
if not env.mobsfy_init():
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Failed to MobSFy the instance')
|
||||
if version < 5:
|
||||
xposed_first_run = True
|
||||
if xposed_first_run:
|
||||
msg = ('Have you MobSFyed the instance before'
|
||||
' attempting Dynamic Analysis?'
|
||||
' Install Framework for Xposed.'
|
||||
' Restart the device and enable'
|
||||
' all Xposed modules. And finally'
|
||||
' restart the device once again.')
|
||||
return print_n_send_error_response(request, msg)
|
||||
# Clean up previous analysis
|
||||
env.dz_cleanup(bin_hash)
|
||||
# Configure Web Proxy
|
||||
env.configure_proxy(package)
|
||||
# Supported in Android 5+
|
||||
env.enable_adb_reverse_tcp(version)
|
||||
# Apply Global Proxy to device
|
||||
env.set_global_proxy(version)
|
||||
# Start Clipboard monitor
|
||||
env.start_clipmon()
|
||||
# Get Screen Resolution
|
||||
screen_width, screen_height = env.get_screen_res()
|
||||
logger.info('Installing APK')
|
||||
app_dir = os.path.join(settings.UPLD_DIR,
|
||||
bin_hash + '/') # APP DIRECTORY
|
||||
apk_path = app_dir + bin_hash + '.apk' # APP PATH
|
||||
env.adb_command(['install', '-r', apk_path], False, True)
|
||||
logger.info('Testing Environment is Ready!')
|
||||
context = {'screen_witdth': screen_width,
|
||||
'screen_height': screen_height,
|
||||
'package': package,
|
||||
'md5': bin_hash,
|
||||
'version': version,
|
||||
'title': 'Dynamic Analyzer'}
|
||||
template = 'dynamic_analysis/android/dynamic_analyzer.html'
|
||||
return render(request, template, context)
|
||||
except Exception:
|
||||
logger.exception('Dynamic Analyzer')
|
||||
return print_n_send_error_response(request,
|
||||
'Dynamic Analysis Failed.')
|
||||
|
||||
|
||||
def httptools_start(request):
|
||||
"""Start httprools UI."""
|
||||
logger.info('Starting httptools Web UI')
|
||||
try:
|
||||
stop_httptools(settings.PROXY_PORT)
|
||||
start_httptools_ui(settings.PROXY_PORT)
|
||||
time.sleep(3)
|
||||
logger.info('httptools UI started')
|
||||
if request.GET['project']:
|
||||
project = request.GET['project']
|
||||
else:
|
||||
project = ''
|
||||
url = ('http://localhost:{}'
|
||||
'/dashboard/{}'.format(
|
||||
str(settings.PROXY_PORT),
|
||||
project))
|
||||
return HttpResponseRedirect(url)
|
||||
except Exception:
|
||||
logger.exception('Starting httptools Web UI')
|
||||
err = 'Error Starting httptools UI'
|
||||
return print_n_send_error_response(request, err)
|
||||
|
||||
|
||||
def logcat(request):
|
||||
logger.info('Starting Logcat streaming')
|
||||
try:
|
||||
pkg = request.GET.get('package')
|
||||
if pkg:
|
||||
if not strict_package_check(pkg):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Invalid package name')
|
||||
template = 'dynamic_analysis/android/logcat.html'
|
||||
return render(request, template, {'package': pkg})
|
||||
app_pkg = request.GET.get('app_package')
|
||||
if app_pkg:
|
||||
if not strict_package_check(app_pkg):
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Invalid package name')
|
||||
adb = os.environ['MOBSF_ADB']
|
||||
g = proc.Group()
|
||||
g.run([adb, 'logcat', app_pkg + ':V', '*:*'])
|
||||
|
||||
def read_process():
|
||||
while g.is_pending():
|
||||
lines = g.readlines()
|
||||
for _, line in lines:
|
||||
time.sleep(.01)
|
||||
yield 'data:{}\n\n'.format(line)
|
||||
return StreamingHttpResponse(read_process(),
|
||||
content_type='text/event-stream')
|
||||
return print_n_send_error_response(
|
||||
request,
|
||||
'Invalid parameters')
|
||||
except Exception:
|
||||
logger.exception('Logcat Streaming')
|
||||
err = 'Error in Logcat streaming'
|
||||
return print_n_send_error_response(request, err)
|
||||
@@ -0,0 +1,404 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Dynamic Analyzer Helpers."""
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
import threading
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from DynamicAnalyzer.tools.webproxy import (get_ca_dir,
|
||||
start_proxy,
|
||||
stop_httptools)
|
||||
|
||||
from StaticAnalyzer.models import StaticAnalyzerAndroid
|
||||
|
||||
from MobSF.utils import (get_adb,
|
||||
get_device,
|
||||
get_proxy_ip,
|
||||
python_list)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Environment:
|
||||
|
||||
def __init__(self, identifier=None):
|
||||
if identifier:
|
||||
self.identifier = identifier
|
||||
else:
|
||||
self.identifier = get_device()
|
||||
self.tools_dir = settings.TOOLS_DIR
|
||||
|
||||
def wait(self, sec):
|
||||
"""Wait in Seconds."""
|
||||
logger.info('Waiting for %s seconds...', str(sec))
|
||||
time.sleep(sec)
|
||||
|
||||
def connect_n_mount(self):
|
||||
"""Test ADB Connection."""
|
||||
self.adb_command(['kill-server'])
|
||||
self.adb_command(['start-server'])
|
||||
logger.info('ADB Restarted')
|
||||
self.wait(2)
|
||||
logger.info('Connecting to Android %s', self.identifier)
|
||||
out = subprocess.check_output([get_adb(), 'connect', self.identifier])
|
||||
if b'unable to connect' in out or b'failed to connect' in out:
|
||||
logger.error('%s', out.decode('utf-8').replace('\n', ''))
|
||||
return False
|
||||
else:
|
||||
logger.info('Remounting /system')
|
||||
self.adb_command(['mount', '-o',
|
||||
'rw,remount', '/system'], True)
|
||||
return True
|
||||
|
||||
def adb_command(self, cmd_list, shell=False, silent=False):
|
||||
"""ADB Command wrapper."""
|
||||
args = [get_adb(),
|
||||
'-s',
|
||||
self.identifier]
|
||||
if shell:
|
||||
args += ['shell']
|
||||
args += cmd_list
|
||||
|
||||
try:
|
||||
result = subprocess.check_output(args)
|
||||
return result
|
||||
except Exception:
|
||||
if not silent:
|
||||
logger.exception('Error Running ADB Command')
|
||||
return None
|
||||
|
||||
def dz_cleanup(self, bin_hash):
|
||||
"""Clean up before Dynamic Analysis."""
|
||||
# Delete ScreenStream Cache
|
||||
screen_file = os.path.join(settings.SCREEN_DIR, 'screen.png')
|
||||
if os.path.exists(screen_file):
|
||||
os.remove(screen_file)
|
||||
# Delete Contents of Screenshot Dir
|
||||
screen_dir = os.path.join(
|
||||
settings.UPLD_DIR, bin_hash + '/screenshots-apk/')
|
||||
if os.path.isdir(screen_dir):
|
||||
shutil.rmtree(screen_dir)
|
||||
else:
|
||||
os.makedirs(screen_dir)
|
||||
|
||||
def configure_proxy(self, project):
|
||||
"""HTTPS Proxy."""
|
||||
proxy_port = settings.PROXY_PORT
|
||||
logger.info('Starting HTTPs Proxy on %s', proxy_port)
|
||||
stop_httptools(proxy_port)
|
||||
start_proxy(proxy_port, project)
|
||||
|
||||
def install_mobsf_ca(self, action):
|
||||
"""Install or Remove MobSF Root CA."""
|
||||
ca_file = os.path.join('/system/etc/security/cacerts/',
|
||||
settings.ROOT_CA)
|
||||
if action == 'install':
|
||||
logger.info('Installing MobSF RootCA')
|
||||
self.adb_command(['push', get_ca_dir(), ca_file])
|
||||
self.adb_command(['chmod',
|
||||
'644',
|
||||
ca_file], True)
|
||||
elif action == 'remove':
|
||||
logger.info('Removing MobSF RootCA')
|
||||
self.adb_command(['rm', ca_file], True)
|
||||
|
||||
def set_global_proxy(self, version):
|
||||
"""Set Global Proxy on device."""
|
||||
# Android 4.4+ supported
|
||||
proxy_ip = None
|
||||
proxy_port = settings.PROXY_PORT
|
||||
if version < 5:
|
||||
proxy_ip = get_proxy_ip(self.identifier)
|
||||
else:
|
||||
proxy_ip = '127.0.0.1'
|
||||
if proxy_ip:
|
||||
if version < 4.4:
|
||||
logger.warning('Please set Android VM proxy as %s:%s',
|
||||
proxy_ip, proxy_port)
|
||||
return
|
||||
logger.info('Setting Global Proxy for Android VM')
|
||||
self.adb_command(
|
||||
['settings',
|
||||
'put',
|
||||
'global',
|
||||
'http_proxy',
|
||||
'{}:{}'.format(proxy_ip, proxy_port)], True)
|
||||
|
||||
def enable_adb_reverse_tcp(self, version):
|
||||
"""Enable ADB Reverse TCP for Proxy."""
|
||||
# Androd 5+ supported
|
||||
if not version >= 5:
|
||||
return
|
||||
proxy_port = settings.PROXY_PORT
|
||||
logger.info('Enabling ADB Reverse TCP on %s', proxy_port)
|
||||
tcp = 'tcp:{}'.format(proxy_port)
|
||||
try:
|
||||
proc = subprocess.Popen([get_adb(),
|
||||
'-s', self.identifier,
|
||||
'reverse', tcp, tcp],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
_, stderr = proc.communicate()
|
||||
if b'error: closed' in stderr:
|
||||
logger.warning('ADB Reverse TCP works only on'
|
||||
' Android 5.0 and above. Please '
|
||||
'configure a reachable IP Address'
|
||||
' in Android proxy settings.')
|
||||
elif stderr:
|
||||
logger.error(stderr.decode('utf-8').replace('\n', ''))
|
||||
except Exception:
|
||||
logger.exception('Enabling ADB Reverse TCP')
|
||||
|
||||
def start_clipmon(self):
|
||||
"""Start Clipboard Monitoring."""
|
||||
logger.info('Starting Clipboard Monitor')
|
||||
args = ['am', 'startservice',
|
||||
'opensecurity.clipdump/.ClipDumper']
|
||||
self.adb_command(args, True)
|
||||
|
||||
def get_screen_res(self):
|
||||
"""Get Screen Resolution of Android Instance."""
|
||||
logger.info('Getting screen resolution')
|
||||
try:
|
||||
resp = self.adb_command(['dumpsys', 'window'], True)
|
||||
scn_rgx = re.compile(r'mUnrestrictedScreen=\(0,0\) .*')
|
||||
scn_rgx2 = re.compile(r'mUnrestricted=\[0,0\]\[.*\]')
|
||||
match = scn_rgx.search(resp.decode('utf-8'))
|
||||
if match:
|
||||
screen_res = match.group().split(' ')[1]
|
||||
width, height = screen_res.split('x', 1)
|
||||
return width, height
|
||||
match = scn_rgx2.search(resp.decode('utf-8'))
|
||||
if match:
|
||||
res = match.group().split('][')[1].replace(']', '')
|
||||
width, height = res.split(',', 1)
|
||||
return width, height
|
||||
else:
|
||||
logger.error('Error getting screen resolution')
|
||||
except Exception:
|
||||
logger.exception('Getting screen resolution')
|
||||
return '1440', '2560'
|
||||
|
||||
def screen_shot(self, outfile):
|
||||
"""Take Screenshot."""
|
||||
self.adb_command(['screencap',
|
||||
'-p',
|
||||
'/data/local/screen.png'], True)
|
||||
self.adb_command(['pull',
|
||||
'/data/local/screen.png',
|
||||
outfile])
|
||||
|
||||
def screen_stream(self):
|
||||
"""Screen Stream."""
|
||||
self.adb_command(['screencap',
|
||||
'-p',
|
||||
'/data/local/stream.png'],
|
||||
True)
|
||||
self.adb_command(['pull',
|
||||
'/data/local/stream.png',
|
||||
'{}screen.png'.format(settings.SCREEN_DIR)])
|
||||
|
||||
def android_component(self, bin_hash, comp):
|
||||
"""Get APK Components."""
|
||||
anddb = StaticAnalyzerAndroid.objects.filter(MD5=bin_hash)
|
||||
resp = []
|
||||
if comp == 'activities':
|
||||
resp = python_list(anddb[0].ACTIVITIES)
|
||||
elif comp == 'receivers':
|
||||
resp = python_list(anddb[0].RECEIVERS)
|
||||
elif comp == 'providers':
|
||||
resp = python_list(anddb[0].PROVIDERS)
|
||||
elif comp == 'services':
|
||||
resp = python_list(anddb[0].SERVICES)
|
||||
elif comp == 'libraries':
|
||||
resp = python_list(anddb[0].LIBRARIES)
|
||||
elif comp == 'exported_activities':
|
||||
resp = python_list(anddb[0].EXPORTED_ACT)
|
||||
return '\n'.join(resp)
|
||||
|
||||
def get_android_version(self):
|
||||
"""Get Android version."""
|
||||
out = self.adb_command(['getprop',
|
||||
'ro.build.version.release'], True)
|
||||
and_version = out.decode('utf-8').rstrip()
|
||||
if and_version.count('.') > 1:
|
||||
and_version = and_version.rsplit('.', 1)[0]
|
||||
if and_version.count('.') > 1:
|
||||
and_version = and_version.split('.', 1)[0]
|
||||
return float(and_version)
|
||||
|
||||
def get_android_arch(self):
|
||||
"""Get Android Architecture."""
|
||||
out = self.adb_command(['getprop',
|
||||
'ro.product.cpu.abi'], True)
|
||||
return out.decode('utf-8').rstrip()
|
||||
|
||||
def launch_n_capture(self, package, activity, outfile):
|
||||
"""Launch and Capture Activity."""
|
||||
self.adb_command(['am',
|
||||
'start',
|
||||
'-n',
|
||||
package + '/' + activity], True)
|
||||
self.wait(3)
|
||||
self.screen_shot(outfile)
|
||||
logger.info('Activity screenshot captured')
|
||||
logger.info('Stopping app')
|
||||
self.adb_command(['am', 'force-stop', package], True)
|
||||
|
||||
def is_mobsfyied(self, android_version):
|
||||
"""Check is Device is MobSFyed."""
|
||||
logger.info('Environment MobSFyed Check')
|
||||
if android_version < 5:
|
||||
agent_file = '.mobsf-x'
|
||||
agent_str = b'MobSF-Xposed'
|
||||
else:
|
||||
agent_file = '.mobsf-f'
|
||||
agent_str = b'MobSF-Frida'
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
[get_adb(),
|
||||
'-s', self.identifier,
|
||||
'shell',
|
||||
'cat',
|
||||
'/system/' + agent_file])
|
||||
if agent_str not in out:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def mobsfy_init(self):
|
||||
"""Init MobSFy."""
|
||||
version = self.get_android_version()
|
||||
logger.info('Android Version identified as %s', version)
|
||||
try:
|
||||
if version < 5:
|
||||
self.xposed_setup(version)
|
||||
self.mobsf_agents_setup('xposed')
|
||||
else:
|
||||
self.frida_setup()
|
||||
self.mobsf_agents_setup('frida')
|
||||
logger.info('MobSFying Completed!')
|
||||
return version
|
||||
except Exception:
|
||||
logger.exception('Failed to MobSFy Android Instance')
|
||||
return False
|
||||
|
||||
def mobsf_agents_setup(self, agent):
|
||||
"""Setup MobSF agents."""
|
||||
# Install MITM RootCA
|
||||
self.install_mobsf_ca('install')
|
||||
# Install MobSF Agents
|
||||
mobsf_agents = 'onDevice/mobsf_agents/'
|
||||
clip_dump = os.path.join(self.tools_dir,
|
||||
mobsf_agents,
|
||||
'ClipDump.apk')
|
||||
logger.info('Installing MobSF Clipboard Dumper')
|
||||
self.adb_command(['install', '-r', clip_dump])
|
||||
if agent == 'frida':
|
||||
agent_file = '.mobsf-f'
|
||||
else:
|
||||
agent_file = '.mobsf-x'
|
||||
mobsf_env = os.path.join(self.tools_dir,
|
||||
mobsf_agents,
|
||||
agent_file)
|
||||
self.adb_command(['push', mobsf_env, '/system/' + agent_file])
|
||||
|
||||
def xposed_setup(self, android_version):
|
||||
"""Setup Xposed."""
|
||||
xposed_dir = 'onDevice/xposed/'
|
||||
xposed_modules = xposed_dir + 'modules/'
|
||||
if android_version < 5:
|
||||
logger.info('Installing Xposed for Kitkat and below')
|
||||
xposed_apk = os.path.join(self.tools_dir,
|
||||
xposed_dir,
|
||||
'Xposed.apk')
|
||||
hooks = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'hooks.json')
|
||||
droidmon = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'Droidmon.apk')
|
||||
logger.info('Installing Droidmon API Analyzer')
|
||||
self.adb_command(['install', '-r', droidmon])
|
||||
logger.info('Copying Droidmon hooks config')
|
||||
self.adb_command(['push', hooks, '/data/local/tmp/'])
|
||||
else:
|
||||
logger.info('Installing Xposed for Lollipop and above')
|
||||
xposed_apk = os.path.join(self.tools_dir,
|
||||
xposed_dir,
|
||||
'XposedInstaller_3.1.5.apk')
|
||||
self.adb_command(['install', '-r', xposed_apk])
|
||||
# Xposed Modules and Support Files
|
||||
justrustme = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'JustTrustMe.apk')
|
||||
rootcloak = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'RootCloak.apk')
|
||||
proxyon = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'mobi.acpm.proxyon_v1_419b04.apk')
|
||||
sslunpin = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'mobi.acpm.sslunpinning_v2_37f44f.apk')
|
||||
bluepill = os.path.join(self.tools_dir,
|
||||
xposed_modules,
|
||||
'AndroidBluePill.apk')
|
||||
logger.info('Installing JustTrustMe')
|
||||
self.adb_command(['install', '-r', justrustme])
|
||||
logger.info('Installing SSLUnpinning')
|
||||
self.adb_command(['install', '-r', sslunpin])
|
||||
logger.info('Installing ProxyOn')
|
||||
self.adb_command(['install', '-r', proxyon])
|
||||
logger.info('Installing RootCloak')
|
||||
self.adb_command(['install', '-r', rootcloak])
|
||||
logger.info('Installing Android BluePill')
|
||||
self.adb_command(['install', '-r', bluepill])
|
||||
logger.info('Launching Xposed Framework.')
|
||||
xposed_installer = ('de.robv.android.xposed.installer/'
|
||||
'de.robv.android.xposed.installer.'
|
||||
'WelcomeActivity')
|
||||
self.adb_command(['am', 'start', '-n',
|
||||
xposed_installer], True)
|
||||
|
||||
def frida_setup(self):
|
||||
"""Setup Frida."""
|
||||
frida_dir = 'onDevice/frida/'
|
||||
frida_bin = os.path.join(self.tools_dir,
|
||||
frida_dir,
|
||||
'frida-server-12.7.4-android-x86')
|
||||
arch = self.get_android_arch()
|
||||
logger.info('Android instance architecture identified as %s', arch)
|
||||
if 'x86' not in arch:
|
||||
logger.error('Make sure a Genymotion Android x86'
|
||||
'instance is running')
|
||||
return
|
||||
logger.info('Copying frida server')
|
||||
self.adb_command(['push', frida_bin, '/system/fd_server'])
|
||||
self.adb_command(['chmod', '755', '/system/fd_server'], True)
|
||||
|
||||
def run_frida_server(self):
|
||||
"""Start Frida Server."""
|
||||
check = self.adb_command(['ps'], True)
|
||||
if b'/system/fd_server' in check:
|
||||
logger.info('Frida Server is already running')
|
||||
return
|
||||
|
||||
def start_frida():
|
||||
fnull = open(os.devnull, 'w')
|
||||
argz = [get_adb(),
|
||||
'-s',
|
||||
self.identifier,
|
||||
'shell',
|
||||
'/system/fd_server']
|
||||
subprocess.call(argz, stdout=fnull, stderr=subprocess.STDOUT)
|
||||
trd = threading.Thread(target=start_frida)
|
||||
trd.daemon = True
|
||||
trd.start()
|
||||
logger.info('Frida Server is running')
|
||||
@@ -0,0 +1,146 @@
|
||||
import os
|
||||
import glob
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import frida
|
||||
|
||||
from DynamicAnalyzer.views.android.environment import Environment
|
||||
from DynamicAnalyzer.views.android.frida_scripts import (
|
||||
class_pattern,
|
||||
class_trace,
|
||||
get_loaded_classes,
|
||||
get_methods,
|
||||
string_catch,
|
||||
string_compare)
|
||||
|
||||
from MobSF.utils import (get_device,
|
||||
is_file_exists)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Frida:
|
||||
|
||||
def __init__(self, app_hash, package, defaults, auxiliary, extras, code):
|
||||
self.hash = app_hash
|
||||
self.package = package
|
||||
self.defaults = defaults
|
||||
self.auxiliary = auxiliary
|
||||
self.extras = extras
|
||||
self.code = code
|
||||
self.frida_dir = os.path.join(settings.TOOLS_DIR,
|
||||
'frida_scripts')
|
||||
self.apk_dir = os.path.join(settings.UPLD_DIR, self.hash + '/')
|
||||
self.api_mon = os.path.join(self.apk_dir, 'mobsf_api_monitor.txt')
|
||||
self.frida_log = os.path.join(self.apk_dir, 'mobsf_frida_out.txt')
|
||||
|
||||
def get_default_scripts(self):
|
||||
"""Get default Frida Scripts."""
|
||||
combined_script = []
|
||||
header = []
|
||||
def_scripts = os.path.join(self.frida_dir, 'default')
|
||||
files = [f for f in glob.glob(
|
||||
def_scripts + '**/*.js', recursive=True)]
|
||||
for item in files:
|
||||
script = Path(item)
|
||||
if script.stem in self.defaults:
|
||||
header.append('send("Loaded Frida Script - {}");'.format(
|
||||
script.stem))
|
||||
combined_script.append(script.read_text())
|
||||
return header + combined_script
|
||||
|
||||
def get_auxiliary(self):
|
||||
"""Get auxiliary hooks."""
|
||||
scripts = []
|
||||
for itm in self.auxiliary:
|
||||
if itm == 'enum_class':
|
||||
scripts.append(get_loaded_classes())
|
||||
elif itm == 'string_catch':
|
||||
scripts.append(string_catch())
|
||||
elif itm == 'string_compare':
|
||||
scripts.append(string_compare())
|
||||
elif itm == 'enum_methods' and 'cls_name' in self.extras:
|
||||
scripts.append(get_methods(self.extras['cls_name']))
|
||||
elif itm == 'search_class' and 'cls_search' in self.extras:
|
||||
scripts.append(class_pattern(self.extras['cls_search']))
|
||||
elif itm == 'trace_class' and 'cls_trace' in self.extras:
|
||||
scripts.append(class_trace(self.extras['cls_trace']))
|
||||
return scripts
|
||||
|
||||
def get_script(self):
|
||||
"""Get final script."""
|
||||
scripts = self.get_default_scripts()
|
||||
scripts.extend(self.get_auxiliary())
|
||||
scripts.extend([self.code])
|
||||
final = 'setTimeout(function() {{ {} }}, 0)'.format(
|
||||
'\n'.join(scripts))
|
||||
return final
|
||||
|
||||
def frida_response(self, message, data):
|
||||
"""Function to handle frida responses."""
|
||||
if 'payload' in message:
|
||||
msg = message['payload']
|
||||
api_mon = 'MobSF-API-Monitor: '
|
||||
aux = '[AUXILIARY] '
|
||||
if not isinstance(msg, str):
|
||||
msg = str(msg)
|
||||
if msg.startswith(api_mon):
|
||||
self.write_log(self.api_mon, msg.replace(api_mon, ''))
|
||||
elif msg.startswith(aux):
|
||||
self.write_log(self.frida_log,
|
||||
msg.replace(aux, '[*] ') + '\n')
|
||||
else:
|
||||
logger.debug('[Frida] %s', msg)
|
||||
self.write_log(self.frida_log, msg + '\n')
|
||||
else:
|
||||
logger.error('[Frida] %s', message)
|
||||
|
||||
def connect(self):
|
||||
"""Connect to Frida Server."""
|
||||
session = None
|
||||
try:
|
||||
env = Environment()
|
||||
self.clean_up()
|
||||
env.run_frida_server()
|
||||
device = frida.get_device(get_device(), settings.FRIDA_TIMEOUT)
|
||||
pid = device.spawn([self.package])
|
||||
device.resume(pid)
|
||||
logger.info('Spawning %s', self.package)
|
||||
time.sleep(2)
|
||||
session = device.attach(pid)
|
||||
except frida.ServerNotRunningError:
|
||||
logger.warning('Frida server is not running')
|
||||
self.connect()
|
||||
except frida.TimedOutError:
|
||||
logger.error('Timed out while waiting for device to appear')
|
||||
except (frida.ProcessNotFoundError,
|
||||
frida.TransportError,
|
||||
frida.InvalidOperationError):
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception('Error Connecting to Frida')
|
||||
try:
|
||||
if session:
|
||||
script = session.create_script(self.get_script())
|
||||
script.on('message', self.frida_response)
|
||||
script.load()
|
||||
sys.stdin.read()
|
||||
script.unload()
|
||||
session.detach()
|
||||
except Exception:
|
||||
logger.exception('Error Connecting to Frida')
|
||||
|
||||
def clean_up(self):
|
||||
if is_file_exists(self.api_mon):
|
||||
os.remove(self.api_mon)
|
||||
if is_file_exists(self.frida_log):
|
||||
os.remove(self.frida_log)
|
||||
|
||||
def write_log(self, file_path, data):
|
||||
with open(file_path, 'a') as flip:
|
||||
flip.write(data)
|
||||
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from DynamicAnalyzer.views.android.operations import strict_package_check
|
||||
|
||||
|
||||
def get_content(file_name):
|
||||
content = ''
|
||||
script = os.path.join(settings.TOOLS_DIR,
|
||||
'frida_scripts',
|
||||
'auxiliary',
|
||||
file_name)
|
||||
|
||||
with open(script, 'r') as scp:
|
||||
content = scp.read()
|
||||
return content
|
||||
|
||||
|
||||
def get_loaded_classes():
|
||||
"""Get Loaded classes."""
|
||||
return get_content('get_loaded_classes.js')
|
||||
|
||||
|
||||
def string_catch():
|
||||
"""Capture all runtime strings."""
|
||||
return get_content('string_catch.js')
|
||||
|
||||
|
||||
def string_compare():
|
||||
"""Capture all runtime string comparisons."""
|
||||
return get_content('string_compare.js')
|
||||
|
||||
|
||||
def get_methods(klazz):
|
||||
"""Get Class methods and implementations."""
|
||||
if not strict_package_check(klazz):
|
||||
return ''
|
||||
content = get_content('get_methods.js')
|
||||
return content.replace('{{CLASS}}', klazz)
|
||||
|
||||
|
||||
def class_pattern(pattern):
|
||||
"""Search in loaded classes based on pattern."""
|
||||
pattern = pattern.replace(
|
||||
'/', '\\/').replace(';', '')
|
||||
content = get_content('search_class_pattern.js')
|
||||
return content.replace('{{PATTERN}}', pattern)
|
||||
|
||||
|
||||
def class_trace(classes):
|
||||
"""Trace all methods of a class."""
|
||||
filtered = []
|
||||
if ',' not in classes:
|
||||
filtered.append(classes.strip())
|
||||
else:
|
||||
for clz in classes.split(','):
|
||||
filtered.append(clz.strip())
|
||||
for klz in filtered:
|
||||
if not strict_package_check(klz):
|
||||
return ''
|
||||
content = get_content('class_trace.js')
|
||||
return content.replace('{{CLASSES}}', str(filtered))
|
||||
@@ -0,0 +1,247 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Dynamic Analyzer Operations."""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from DynamicAnalyzer.views.android.environment import Environment
|
||||
|
||||
from MobSF.utils import (get_adb, get_device, is_number)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Helpers
|
||||
|
||||
|
||||
def json_response(data):
|
||||
"""Return JSON Response."""
|
||||
return HttpResponse(json.dumps(data),
|
||||
content_type='application/json')
|
||||
|
||||
|
||||
def is_attack_pattern(user_input):
|
||||
"""Check for attacks."""
|
||||
atk_pattern = re.compile(r';|\$\(|\|\||&&')
|
||||
stat = re.findall(atk_pattern, user_input)
|
||||
if stat:
|
||||
logger.error('Possible RCE attack detected')
|
||||
return stat
|
||||
|
||||
|
||||
def strict_package_check(user_input):
|
||||
"""Strict package name check."""
|
||||
pat = re.compile(r'^\w+\.*[\w\.\$]+$')
|
||||
resp = re.match(pat, user_input)
|
||||
if not resp:
|
||||
logger.error('Invalid package/class name')
|
||||
return resp
|
||||
|
||||
|
||||
def is_path_traversal(user_input):
|
||||
"""Check for path traversal."""
|
||||
if (('../' in user_input)
|
||||
or ('%2e%2e' in user_input)
|
||||
or ('..' in user_input)
|
||||
or ('%252e' in user_input)):
|
||||
logger.error('Path traversal attack detected')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_md5(user_input):
|
||||
"""Check if string is valid MD5."""
|
||||
stat = re.match(r'^[0-9a-f]{32}$', user_input)
|
||||
if not stat:
|
||||
logger.error('Invalid scan hash')
|
||||
return stat
|
||||
|
||||
|
||||
def invalid_params():
|
||||
"""Standard response for invalid params."""
|
||||
msg = 'Invalid Parameters'
|
||||
logger.error(msg)
|
||||
data = {'status': 'failed', 'message': msg}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def mobsfy(request):
|
||||
"""Configure Instance for Dynamic Analysis."""
|
||||
logger.info('MobSFying Android instance')
|
||||
data = {}
|
||||
try:
|
||||
identifier = request.POST['identifier']
|
||||
create_env = Environment(identifier)
|
||||
if not create_env.connect_n_mount():
|
||||
msg = 'Connection failed'
|
||||
data = {'status': 'failed', 'message': msg}
|
||||
return json_response(data)
|
||||
version = create_env.mobsfy_init()
|
||||
if not version:
|
||||
msg = 'Connection failed'
|
||||
data = {'status': 'failed', 'message': msg}
|
||||
return json_response(data)
|
||||
else:
|
||||
data = {'status': 'ok', 'version': version}
|
||||
except Exception as exp:
|
||||
logger.exception('MobSFying Android instance failed')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def execute_adb(request):
|
||||
"""Execute ADB Commands."""
|
||||
data = {'status': 'ok', 'message': ''}
|
||||
cmd = request.POST['cmd']
|
||||
if cmd:
|
||||
args = [get_adb(),
|
||||
'-s',
|
||||
get_device()]
|
||||
try:
|
||||
proc = subprocess.Popen(args + cmd.split(' '),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
except Exception:
|
||||
logger.exception('Executing ADB Commands')
|
||||
if stdout or stderr:
|
||||
out = stdout or stderr
|
||||
out = out.decode('utf8', 'ignore')
|
||||
else:
|
||||
out = ''
|
||||
data = {'status': 'ok', 'message': out}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def get_component(request):
|
||||
"""Get Android Component."""
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
comp = request.POST['component']
|
||||
bin_hash = request.POST['hash']
|
||||
if is_attack_pattern(comp) or not is_md5(bin_hash):
|
||||
return invalid_params()
|
||||
comp = env.android_component(bin_hash, comp)
|
||||
data = {'status': 'ok', 'message': comp}
|
||||
except Exception as exp:
|
||||
logger.exception('Getting Android Component')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def take_screenshot(request):
|
||||
"""Take Screenshot."""
|
||||
logger.info('Taking screenshot')
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
bin_hash = request.POST['hash']
|
||||
if not is_md5(bin_hash):
|
||||
return invalid_params()
|
||||
data = {}
|
||||
rand_int = random.randint(1, 1000000)
|
||||
screen_dir = os.path.join(settings.UPLD_DIR,
|
||||
bin_hash + '/screenshots-apk/')
|
||||
if not os.path.exists(screen_dir):
|
||||
os.makedirs(screen_dir)
|
||||
outile = '{}screenshot-{}.png'.format(
|
||||
screen_dir,
|
||||
str(rand_int))
|
||||
env.screen_shot(outile)
|
||||
logger.info('Screenshot captured')
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('Taking screenshot')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def screen_cast(request):
|
||||
"""ScreenCast."""
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
trd = threading.Thread(target=env.screen_stream)
|
||||
trd.daemon = True
|
||||
trd.start()
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('Screen streaming')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def touch(request):
|
||||
"""Sending Touch Events."""
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
x_axis = request.POST['x']
|
||||
y_axis = request.POST['y']
|
||||
if not is_number(x_axis) and not is_number(y_axis):
|
||||
logger.error('Axis parameters must be numbers')
|
||||
return invalid_params()
|
||||
args = ['input',
|
||||
'tap',
|
||||
x_axis,
|
||||
y_axis]
|
||||
trd = threading.Thread(target=env.adb_command,
|
||||
args=(args, True))
|
||||
trd.daemon = True
|
||||
trd.start()
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('Sending Touch Events')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def mobsf_ca(request):
|
||||
"""Install and Remove MobSF Proxy RootCA."""
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
action = request.POST['action']
|
||||
if action == 'install':
|
||||
env.install_mobsf_ca(action)
|
||||
data = {'status': 'ok', 'message': 'installed'}
|
||||
elif action == 'remove':
|
||||
env.install_mobsf_ca(action)
|
||||
data = {'status': 'ok', 'message': 'removed'}
|
||||
else:
|
||||
data = {'status': 'failed',
|
||||
'message': 'Action not supported'}
|
||||
except Exception as exp:
|
||||
logger.exception('MobSF RootCA Handler')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Dynamic Analyzer Reporting."""
|
||||
import logging
|
||||
import ntpath
|
||||
import os
|
||||
import io
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
from django.template.defaulttags import register
|
||||
from django.utils.html import escape
|
||||
|
||||
from DynamicAnalyzer.views.android.analysis import (generate_download,
|
||||
get_screenshots,
|
||||
run_analysis)
|
||||
from DynamicAnalyzer.views.android.operations import (is_attack_pattern,
|
||||
is_md5,
|
||||
is_path_traversal)
|
||||
from DynamicAnalyzer.views.android.tests_xposed import droidmon_api_analysis
|
||||
from DynamicAnalyzer.views.android.tests_frida import apimon_analysis
|
||||
|
||||
from MobSF.utils import (is_file_exists,
|
||||
print_n_send_error_response,
|
||||
read_sqlite)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register.filter
|
||||
def key(d, key_name):
|
||||
"""To get dict element by key name in template."""
|
||||
return d.get(key_name)
|
||||
|
||||
|
||||
def view_report(request):
|
||||
"""Dynamic Analysis Report Generation."""
|
||||
logger.info('Dynamic Analysis Report Generation')
|
||||
try:
|
||||
md5_hash = request.GET['hash']
|
||||
package = request.GET['package']
|
||||
droidmon = {}
|
||||
apimon = {}
|
||||
if (is_attack_pattern(package)
|
||||
or not is_md5(md5_hash)):
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Parameters')
|
||||
app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
download_dir = settings.DWD_DIR
|
||||
if not is_file_exists(os.path.join(app_dir, 'logcat.txt')):
|
||||
msg = ('Dynamic Analysis report is not available '
|
||||
'for this app. Perform Dynamic Analysis '
|
||||
'and generate the report.')
|
||||
return print_n_send_error_response(request, msg)
|
||||
fd_log = os.path.join(app_dir, 'mobsf_frida_out.txt')
|
||||
droidmon = droidmon_api_analysis(app_dir, package)
|
||||
apimon = apimon_analysis(app_dir)
|
||||
analysis_result = run_analysis(app_dir, md5_hash, package)
|
||||
generate_download(app_dir, md5_hash, download_dir, package)
|
||||
images = get_screenshots(md5_hash, download_dir)
|
||||
context = {'md5': md5_hash,
|
||||
'emails': analysis_result['emails'],
|
||||
'urls': analysis_result['urls'],
|
||||
'domains': analysis_result['domains'],
|
||||
'clipboard': analysis_result['clipboard'],
|
||||
'xml': analysis_result['xml'],
|
||||
'sqlite': analysis_result['sqlite'],
|
||||
'others': analysis_result['other_files'],
|
||||
'screenshots': images['screenshots'],
|
||||
'acttest': images['activities'],
|
||||
'expacttest': images['exported_activities'],
|
||||
'droidmon': droidmon,
|
||||
'apimon': apimon,
|
||||
'fdlog': is_file_exists(fd_log),
|
||||
'package': package,
|
||||
'title': 'Dynamic Analysis'}
|
||||
template = 'dynamic_analysis/android/dynamic_report.html'
|
||||
return render(request, template, context)
|
||||
except Exception as exp:
|
||||
logger.exception('Dynamic Analysis Report Generation')
|
||||
err = 'Error Geneating Dynamic Analysis Report. ' + str(exp)
|
||||
return print_n_send_error_response(request, err)
|
||||
|
||||
|
||||
def view_file(request):
|
||||
"""View File."""
|
||||
logger.info('Viewing File')
|
||||
try:
|
||||
typ = ''
|
||||
rtyp = ''
|
||||
dat = ''
|
||||
sql_dump = {}
|
||||
fil = request.GET['file']
|
||||
md5_hash = request.GET['md5']
|
||||
typ = request.GET['type']
|
||||
if not is_md5(md5_hash):
|
||||
return print_n_send_error_response(request,
|
||||
'Invalid Parameters')
|
||||
if is_path_traversal(fil):
|
||||
err = 'Path Traversal Attack Detected'
|
||||
return print_n_send_error_response(request, err)
|
||||
src = os.path.join(
|
||||
settings.UPLD_DIR,
|
||||
md5_hash,
|
||||
'DYNAMIC_DeviceData/')
|
||||
sfile = os.path.join(src, fil)
|
||||
with io.open(sfile, mode='r', encoding='ISO-8859-1') as flip:
|
||||
dat = flip.read()
|
||||
if fil.endswith('.xml') and typ == 'xml':
|
||||
rtyp = 'xml'
|
||||
elif typ == 'db':
|
||||
sql_dump = read_sqlite(sfile)
|
||||
rtyp = 'asciidoc'
|
||||
elif typ == 'others':
|
||||
rtyp = 'asciidoc'
|
||||
else:
|
||||
err = 'File type not supported'
|
||||
return print_n_send_error_response(request, err)
|
||||
fil = escape(ntpath.basename(fil))
|
||||
context = {'title': fil,
|
||||
'file': fil,
|
||||
'dat': dat,
|
||||
'sql': sql_dump,
|
||||
'type': rtyp}
|
||||
template = 'general/view.html'
|
||||
return render(request, template, context)
|
||||
except Exception:
|
||||
logger.exception('Viewing File')
|
||||
return print_n_send_error_response(request, 'Error Viewing File')
|
||||
@@ -1,121 +0,0 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Shared Functions for Android Dynamic Analyzer."""
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from MobSF.utils import get_adb
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def wait(sec):
|
||||
"""Wait in Seconds."""
|
||||
logger.info('Waiting for %s seconds...', str(sec))
|
||||
time.sleep(sec)
|
||||
|
||||
|
||||
def get_res():
|
||||
"""Get Screen Resolution or Device or VM."""
|
||||
logger.info('Getting Screen Resolution')
|
||||
try:
|
||||
adb = get_adb()
|
||||
resp = subprocess.check_output(
|
||||
[adb, '-s', get_identifier(), 'shell', 'dumpsys', 'window'])
|
||||
resp = resp.decode('utf-8').split('\n')
|
||||
res = ''
|
||||
for line in resp:
|
||||
if 'mUnrestrictedScreen' in line:
|
||||
res = line
|
||||
break
|
||||
res = res.split('(0,0)')[1]
|
||||
res = res.strip()
|
||||
res = res.split('x')
|
||||
if len(res) == 2:
|
||||
return res[0], res[1]
|
||||
# width, height
|
||||
return '', ''
|
||||
except Exception:
|
||||
logger.exception('Getting Screen Resolution')
|
||||
return '', ''
|
||||
|
||||
|
||||
def get_identifier():
|
||||
"""Get Device Type."""
|
||||
try:
|
||||
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
|
||||
return settings.DEVICE_IP + ':' + str(settings.DEVICE_ADB_PORT)
|
||||
else:
|
||||
return settings.VM_IP + ':' + str(settings.VM_ADB_PORT)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
'Getting ADB Connection Identifier for Device/VM')
|
||||
|
||||
|
||||
def adb_command(cmd_list, shell=False, silent=False):
|
||||
emulator = get_identifier()
|
||||
adb = get_adb()
|
||||
|
||||
args = [adb,
|
||||
'-s',
|
||||
emulator]
|
||||
if shell:
|
||||
args += ['shell']
|
||||
args += cmd_list
|
||||
|
||||
try:
|
||||
result = subprocess.check_output(args)
|
||||
return result
|
||||
except Exception:
|
||||
if not silent:
|
||||
logger.exception('Running ADB Command')
|
||||
return None
|
||||
|
||||
|
||||
def connect():
|
||||
"""Connect to VM/Device."""
|
||||
logger.info('Connecting to VM/Device')
|
||||
adb = get_adb()
|
||||
subprocess.call([adb, 'kill-server'])
|
||||
subprocess.call([adb, 'start-server'])
|
||||
logger.info('ADB Started')
|
||||
wait(5)
|
||||
logger.info('Connecting to VM/Device')
|
||||
out = subprocess.check_output([adb, 'connect', get_identifier()])
|
||||
if b'unable to connect' in out:
|
||||
raise ValueError('ERROR Connecting to VM/Device. ',
|
||||
out.decode('utf-8').replace('\n', ''))
|
||||
try:
|
||||
subprocess.call([adb, '-s', get_identifier(), 'wait-for-device'])
|
||||
logger.info('Mounting')
|
||||
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
|
||||
adb_command(['su', '-c', 'mount', '-o',
|
||||
'rw,remount,rw', '/system'], True)
|
||||
else:
|
||||
adb_command(['su', '-c', 'mount', '-o',
|
||||
'rw,remount,rw', '/system'], True)
|
||||
# This may not work for VMs other than the default MobSF VM
|
||||
adb_command(['mount', '-o', 'rw,remount', '-t', 'rfs',
|
||||
'/dev/block/sda6', '/system'], True)
|
||||
except Exception:
|
||||
logger.exception('Connecting to VM/Device')
|
||||
|
||||
|
||||
def install_and_run(apk_path, package, launcher, is_activity):
|
||||
"""Install APK and Run it."""
|
||||
logger.info('Starting App for Dynamic Analysis')
|
||||
try:
|
||||
logger.info('Installing APK')
|
||||
adb_command(['install', '-r', apk_path])
|
||||
if is_activity:
|
||||
run_app = package + '/' + launcher
|
||||
logger.info('Launching APK Main Activity')
|
||||
adb_command(['am', 'start', '-n', run_app], True)
|
||||
else:
|
||||
logger.info('App Doesn\'t have a Main Activity')
|
||||
# Handle Service or Give Choice to Select in Future.
|
||||
logger.info('Testing Environment is Ready!')
|
||||
except Exception:
|
||||
logger.exception('Starting App for Dynamic Analysis')
|
||||
@@ -0,0 +1,158 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Available Actions."""
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from DynamicAnalyzer.views.android.operations import (
|
||||
invalid_params,
|
||||
is_attack_pattern,
|
||||
is_md5,
|
||||
json_response,
|
||||
strict_package_check)
|
||||
from DynamicAnalyzer.views.android.environment import Environment
|
||||
from DynamicAnalyzer.views.android.tests_xposed import download_xposed_log
|
||||
from DynamicAnalyzer.tools.webproxy import stop_httptools
|
||||
|
||||
from MobSF.utils import python_list
|
||||
|
||||
from StaticAnalyzer.models import StaticAnalyzerAndroid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def activity_tester(request):
|
||||
"""Exported & non exported activity Tester."""
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
test = request.POST['test']
|
||||
md5_hash = request.POST['hash']
|
||||
package = request.POST['package']
|
||||
if is_attack_pattern(package) or not is_md5(md5_hash):
|
||||
return invalid_params()
|
||||
app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
screen_dir = os.path.join(app_dir, 'screenshots-apk/')
|
||||
if not os.path.exists(screen_dir):
|
||||
os.makedirs(screen_dir)
|
||||
static_android_db = StaticAnalyzerAndroid.objects.filter(
|
||||
MD5=md5_hash)
|
||||
if not static_android_db.exists():
|
||||
data = {'status': 'failed',
|
||||
'message': 'App details not found in database'}
|
||||
return json_response(data)
|
||||
iden = ''
|
||||
if test == 'exported':
|
||||
iden = 'Exported '
|
||||
logger.info('Exported activity tester')
|
||||
activities = python_list(static_android_db[0].EXPORTED_ACT)
|
||||
else:
|
||||
logger.info('Activity tester')
|
||||
activities = python_list(static_android_db[0].ACTIVITIES)
|
||||
logger.info('Fetching %sactivities for %s', iden, package)
|
||||
if not activities:
|
||||
msg = 'No {}Activites found'.format(iden)
|
||||
logger.info(msg)
|
||||
data = {'status': 'failed',
|
||||
'message': msg}
|
||||
return json_response(data)
|
||||
act_no = 0
|
||||
logger.info('Starting %sActivity Tester...', iden)
|
||||
logger.info('%s %sActivities Identified',
|
||||
str(len(activities)), iden)
|
||||
for activity in activities:
|
||||
act_no += 1
|
||||
logger.info(
|
||||
'Launching %sActivity - %s. %s',
|
||||
iden,
|
||||
str(act_no),
|
||||
activity)
|
||||
if test == 'exported':
|
||||
file_iden = 'expact'
|
||||
else:
|
||||
file_iden = 'act'
|
||||
outfile = ('{}{}-{}.png'.format(
|
||||
screen_dir,
|
||||
file_iden,
|
||||
act_no))
|
||||
env.launch_n_capture(package, activity, outfile)
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('%sActivity tester', iden)
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def download_data(request):
|
||||
"""Download Application Data from Device."""
|
||||
logger.info('Downloading app data')
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
package = request.POST['package']
|
||||
md5_hash = request.POST['hash']
|
||||
if is_attack_pattern(package) or not is_md5(md5_hash):
|
||||
return invalid_params()
|
||||
apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
stop_httptools(settings.PROXY_PORT)
|
||||
files_loc = '/data/local/'
|
||||
logger.info('Archiving files created by app')
|
||||
env.adb_command(['tar', '-cvf', files_loc + package + '.tar',
|
||||
'/data/data/' + package + '/'], True)
|
||||
logger.info('Downloading Archive')
|
||||
env.adb_command(['pull', files_loc + package + '.tar',
|
||||
apk_dir + package + '.tar'])
|
||||
logger.info('Stopping ADB server')
|
||||
env.adb_command(['kill-server'])
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('Downloading application data')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def collect_logs(request):
|
||||
"""Collecting Data and Cleanup."""
|
||||
logger.info('Collecting Data and Cleaning Up')
|
||||
data = {}
|
||||
try:
|
||||
env = Environment()
|
||||
md5_hash = request.POST['hash']
|
||||
package = request.POST['package']
|
||||
if (not strict_package_check(package)
|
||||
or not is_md5(md5_hash)):
|
||||
return invalid_params()
|
||||
apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
|
||||
lout = os.path.join(apk_dir, 'logcat.txt')
|
||||
dout = os.path.join(apk_dir, 'dump.txt')
|
||||
logger.info('Downloading logcat logs')
|
||||
logcat = env.adb_command(['logcat',
|
||||
'-d',
|
||||
package + ':V',
|
||||
'*:*'])
|
||||
with open(lout, 'wb') as flip:
|
||||
flip.write(logcat)
|
||||
logger.info('Downloading dumpsys logs')
|
||||
dumpsys = env.adb_command(['dumpsys'], True)
|
||||
with open(dout, 'wb') as flip:
|
||||
flip.write(dumpsys)
|
||||
if env.get_android_version() < 5:
|
||||
download_xposed_log(apk_dir)
|
||||
env.adb_command(['am', 'force-stop', package], True)
|
||||
logger.info('Stopping app')
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('Data Collection & Clean Up failed')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
@@ -0,0 +1,216 @@
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Frida tests."""
|
||||
import base64
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
from pathlib import Path
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.conf import settings
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from DynamicAnalyzer.views.android.frida_core import Frida
|
||||
from DynamicAnalyzer.views.android.operations import (
|
||||
invalid_params,
|
||||
is_attack_pattern,
|
||||
is_md5,
|
||||
json_response,
|
||||
strict_package_check)
|
||||
|
||||
from MobSF.utils import (
|
||||
is_file_exists,
|
||||
is_safe_path,
|
||||
print_n_send_error_response)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['GET'])
|
||||
def list_frida_scripts(request):
|
||||
"""Get frida scripts from others."""
|
||||
scripts = []
|
||||
others = os.path.join(settings.TOOLS_DIR,
|
||||
'frida_scripts',
|
||||
'others')
|
||||
files = [f for f in glob.glob(
|
||||
others + '**/*.js', recursive=True)]
|
||||
for item in files:
|
||||
scripts.append(Path(item).stem)
|
||||
return json_response({'status': 'ok',
|
||||
'files': scripts})
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def get_script(request):
|
||||
"""Get frida scripts from others."""
|
||||
data = {'status': 'ok', 'content': ''}
|
||||
try:
|
||||
scripts = request.POST.getlist('scripts[]')
|
||||
others = os.path.join(settings.TOOLS_DIR,
|
||||
'frida_scripts',
|
||||
'others')
|
||||
script_ct = []
|
||||
for script in scripts:
|
||||
script_file = os.path.join(others, script + '.js')
|
||||
if not is_safe_path(others, script_file):
|
||||
return json_response(data)
|
||||
if is_file_exists(script_file):
|
||||
script_ct.append(Path(script_file).read_text())
|
||||
data['content'] = '\n'.join(script_ct)
|
||||
except Exception:
|
||||
pass
|
||||
return json_response(data)
|
||||
# AJAX
|
||||
|
||||
|
||||
@require_http_methods(['POST'])
|
||||
def instrument(request):
|
||||
"""Instrument app with frida."""
|
||||
data = {}
|
||||
try:
|
||||
logger.info('Starting Instrumentation')
|
||||
package = request.POST['package']
|
||||
md5_hash = request.POST['hash']
|
||||
default_hooks = request.POST['default_hooks']
|
||||
auxiliary_hooks = request.POST['auxiliary_hooks']
|
||||
code = request.POST['frida_code']
|
||||
# Fill extras
|
||||
extras = {}
|
||||
class_name = request.POST.get('cls_name')
|
||||
if class_name:
|
||||
extras['cls_name'] = class_name.strip()
|
||||
class_search = request.POST.get('cls_search')
|
||||
if class_search:
|
||||
extras['cls_search'] = class_search.strip()
|
||||
cls_trace = request.POST.get('cls_trace')
|
||||
if cls_trace:
|
||||
extras['cls_trace'] = cls_trace.strip()
|
||||
if (is_attack_pattern(default_hooks)
|
||||
or not strict_package_check(package)
|
||||
or not is_md5(md5_hash)):
|
||||
return invalid_params()
|
||||
frida_obj = Frida(md5_hash,
|
||||
package,
|
||||
default_hooks.split(','),
|
||||
auxiliary_hooks.split(','),
|
||||
extras,
|
||||
code)
|
||||
trd = threading.Thread(target=frida_obj.connect)
|
||||
trd.daemon = True
|
||||
trd.start()
|
||||
data = {'status': 'ok'}
|
||||
except Exception as exp:
|
||||
logger.exception('Instrumentation failed')
|
||||
data = {'status': 'failed', 'message': str(exp)}
|
||||
return json_response(data)
|
||||
|
||||
|
||||
def live_api(request):
|
||||
try:
|
||||
apphash = request.GET.get('hash', '')
|
||||
stream = request.GET.get('stream', '')
|
||||
if not is_md5(apphash):
|
||||
return invalid_params()
|
||||
if stream:
|
||||
apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
|
||||
apimon_file = os.path.join(apk_dir, 'mobsf_api_monitor.txt')
|
||||
data = {}
|
||||
if is_file_exists(apimon_file):
|
||||
with open(apimon_file, 'r') as flip:
|
||||
api_list = json.loads('[{}]'.format(
|
||||
flip.read()[:-1]))
|
||||
data = {'data': api_list}
|
||||
return json_response(data)
|
||||
logger.info('Starting API monitor streaming')
|
||||
template = 'dynamic_analysis/android/live_api.html'
|
||||
return render(request,
|
||||
template,
|
||||
{'hash': apphash,
|
||||
'package': request.GET.get('package', ''),
|
||||
'title': 'Live API Monitor'})
|
||||
except Exception:
|
||||
logger.exception('API monitor streaming')
|
||||
err = 'Error in API monitor streaming'
|
||||
return print_n_send_error_response(request, err)
|
||||
|
||||
|
||||
def frida_logs(request):
|
||||
try:
|
||||
apphash = request.GET.get('hash', '')
|
||||
stream = request.GET.get('stream', '')
|
||||
if not is_md5(apphash):
|
||||
return invalid_params()
|
||||
if stream:
|
||||
apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
|
||||
frida_logs = os.path.join(apk_dir, 'mobsf_frida_out.txt')
|
||||
data = {}
|
||||
if is_file_exists(frida_logs):
|
||||
with open(frida_logs, 'r') as flip:
|
||||
data = {'data': flip.read()}
|
||||
return json_response(data)
|
||||
logger.info('Frida Logs live streaming')
|
||||
template = 'dynamic_analysis/android/frida_logs.html'
|
||||
return render(request,
|
||||
template,
|
||||
{'hash': apphash,
|
||||
'package': request.GET.get('package', ''),
|
||||
'title': 'Live Frida logs'})
|
||||
except Exception:
|
||||
logger.exception('Frida log streaming')
|
||||
err = 'Error in Frida log streaming'
|
||||
return print_n_send_error_response(request, err)
|
||||
|
||||
|
||||
def decode_base64(data, altchars=b'+/'):
|
||||
"""Decode base64, padding being optional.
|
||||
|
||||
:param data: Base64 data as an ASCII byte string
|
||||
:returns: The decoded byte string.
|
||||
|
||||
"""
|
||||
data = re.sub(rb'[^a-zA-Z0-9%s]+' % altchars,
|
||||
b'', data.encode())
|
||||
missing_padding = len(data) % 4
|
||||
if missing_padding:
|
||||
data += b'=' * (4 - missing_padding)
|
||||
return base64.b64decode(data, altchars)
|
||||
|
||||
|
||||
def apimon_analysis(app_dir):
|
||||
"""API Analysis."""
|
||||
api_details = {}
|
||||
try:
|
||||
location = os.path.join(app_dir, 'mobsf_api_monitor.txt')
|
||||
if not is_file_exists(location):
|
||||
return {}
|
||||
logger.info('Frida API Monitor Analysis')
|
||||
with open(location, 'r') as flip:
|
||||
apis = json.loads('[{}]'.format(
|
||||
flip.read()[:-1]))
|
||||
for api in apis:
|
||||
to_decode = None
|
||||
if (api['class'] == 'android.util.Base64'
|
||||
and (api['method'] == 'encodeToString')):
|
||||
to_decode = api['returnValue'].replace('"', '')
|
||||
elif (api['class'] == 'android.util.Base64'
|
||||
and api['method'] == 'decode'):
|
||||
to_decode = api['arguments'][0]
|
||||
try:
|
||||
if to_decode:
|
||||
api['decoded'] = decode_base64(to_decode)
|
||||
except Exception:
|
||||
pass
|
||||
if api['name'] in api_details:
|
||||
api_details[api['name']].append(api)
|
||||
else:
|
||||
api_details[api['name']] = [api]
|
||||
except Exception:
|
||||
logger.exception('API Monitor Analysis')
|
||||
return api_details
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user