# -*- coding: utf-8 -*-
# Trueseeing: Non-decompiling Android application vulnerability scanner
# Copyright (C) 2017 Takahiro Yoshimura <takahiro_y@monolithworks.co.jp>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import glob
import os
import re
import tempfile
import logging
import shutil

import pkg_resources
import lxml.etree as ET

from trueseeing.context import Context

log = logging.getLogger(__name__)

class SigningKey:
  def __init__(self):
    pass
  def key(self):
    path = os.path.join(os.environ['HOME'], '.android', 'debug.keystore')
    if os.path.exists(path):
      return path
    else:
      os.makedirs(os.dirname(path))
      log.info("generating key for repackaging")
      os.system('keytool -genkey -v -keystore %(path)s -alias androiddebugkey -dname "CN=Android Debug, O=Android, C=US" -storepass android -keypass android -keyalg RSA -keysize 2048 -validity 10000' % dict(path=path))
      return path

class ExploitUnsign:
  def __init__(self, apk, out):
    self.apk = os.path.realpath(apk)
    self.out = out

  def exploit(self):
    # XXX insecure
    with tempfile.TemporaryDirectory() as d:
      os.system("(mkdir -p %(root)s/t)" % dict(root=d, apk=self.apk))
      os.system("(cd %(root)s/t && unzip -q %(apk)s && rm -rf META-INF && zip -qr ../unsigned.apk .)" % dict(root=d, apk=self.apk))
      shutil.copyfile(os.path.join(d, 'unsigned.apk'), self.out)

class ExploitResign:
  def __init__(self, apk, out):
    self.apk = os.path.realpath(apk)
    self.out = out

  def exploit(self):
    # XXX insecure
    with tempfile.TemporaryDirectory() as d:
      os.system("(mkdir -p %(root)s/t)" % dict(root=d, apk=self.apk))
      os.system("(cd %(root)s/t && unzip -q %(apk)s)" % dict(root=d, apk=self.apk))
      sigfile = self.sigfile(d)
      os.system("(cd %(root)s/t && rm -rf META-INF && zip -qr ../signed.apk .)" % dict(root=d, apk=self.apk))
      os.system("(cd %(root)s && jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore %(keystore)s -storepass android -keypass android -sigfile %(sigfile)s signed.apk androiddebugkey)" % dict(root=d, keystore=SigningKey().key(), sigfile=sigfile))
      shutil.copyfile(os.path.join(d, 'signed.apk'), self.out)

  def sigfile(self, root):
    try:
      fn = [os.path.basename(fn) for fn in glob.glob("%(root)s/t/META-INF/*.SF" % dict(root=root))][0]
      log.debug("found existing signature: %s" % fn)
      return re.sub(r'\.[A-Z]+$', '', fn)
    except IndexError:
      log.debug("signature not found")
      return 'CERT'


class Patches:
  def __init__(self, apk, out):
    self.apk = os.path.realpath(apk)
    self.out = out

  def exploit(self):
    with Context() as context:
      context.analyze(self.apk)
      log.info("%s -> %s" % (self.apk, context.wd))
      self.patched(context)

      # XXX
      sigfile = 'CERT'

      # XXX insecure
      with tempfile.TemporaryDirectory() as d:
        os.system("(mkdir -p %(root)s/)" % dict(root=d, apk=self.apk))
        os.system("(cd %(wd)s && java -jar %(apktool)s b -o %(root)s/patched.apk .)" % dict(root=d, apktool=pkg_resources.resource_filename(__name__, os.path.join('libs', 'apktool.jar')), wd=context.wd))
        os.system("(cd %(root)s && jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore %(keystore)s -storepass android -keypass android -sigfile %(sigfile)s patched.apk androiddebugkey)" % dict(root=d, keystore=SigningKey().key(), sigfile=sigfile))
        shutil.copyfile(os.path.join(d, 'patched.apk'), self.out)

class ExploitEnableDebug(Patches):
  def patched(self, context):
    manifest = context.parsed_manifest()
    for e in manifest.xpath('.//application'):
      e.attrib['{http://schemas.android.com/apk/res/android}debuggable'] = "true"
    with open(os.path.join(context.wd, 'AndroidManifest.xml'), 'wb') as f:
      f.write(ET.tostring(manifest))

class ExploitEnableBackup(Patches):
  def patched(self, context):
    manifest = context.parsed_manifest()
    for e in manifest.xpath('.//application'):
      e.attrib['{http://schemas.android.com/apk/res/android}allowBackup'] = "true"
    with open(os.path.join(context.wd, 'AndroidManifest.xml'), 'wb') as f:
      f.write(ET.tostring(manifest))

class ExploitDisablePinning(Patches):
  def patched(self, context):
    manifest = context.parsed_manifest()
    for e in manifest.xpath('.//application'):
      e.attrib['{http://schemas.android.com/apk/res/android}networkSecurityConfig'] = "@xml/network_security_config"
    with open(os.path.join(context.wd, 'AndroidManifest.xml'), 'wb') as f:
      f.write(ET.tostring(manifest))
    os.makedirs(os.path.join(context.wd, 'res', 'xml'), exist_ok=True)
    with open(os.path.join(context.wd, 'res','xml','network_security_config.xml'), 'wb') as f:
      f.write(b'''\
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <base-config cleartextTrafficPermitted="true">
    <trust-anchors>
      <certificates src="system" />
      <certificates src="user" />
    </trust-anchors>
  </base-config>
</network-security-config>
''')
