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:
Ajin Abraham
2019-09-22 06:53:08 +05:30
committed by GitHub
parent cfa4c16b91
commit 595c534576
163 changed files with 58787 additions and 278602 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
sonar.sources=.
sonar.exclusions=static/**/*,templates/**/*,DynamicAnalyzer/tools/adb/**/*
sonar.exclusions=static/**/*,templates/**/*
sonar.sourceEncoding=UTF-8
+2 -2
View File
@@ -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
View File
@@ -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.
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())
@@ -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
@@ -0,0 +1 @@
MobSF-Frida
@@ -0,0 +1 @@
MobSF-Xposed
+389
View File
@@ -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
}
+40 -36
View File
@@ -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
+189 -246
View 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')
-945
View File
@@ -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
View File
@@ -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')
+146
View File
@@ -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))
+247
View File
@@ -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)
+129
View File
@@ -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')
-121
View 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