# -*- coding: utf-8 -*-
# Trueseeing: Non-decompiling Android application vulnerability scanner
# Copyright (C) 2017-22 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/>.

from __future__ import annotations
from typing import TYPE_CHECKING

import os

from trueseeing.core.ui import ui

if TYPE_CHECKING:
  from typing import List
  from trueseeing.core.context import Context
  from trueseeing.core.patch import Patch, Patcher
  from trueseeing.core.sign import Unsigner, Resigner

class ExploitMode:
  _files: List[str]
  def __init__(self, files: List[str]) -> None:
    self._files = files

  def invoke(self, mode: str) -> int:
    for f in self._files:
      if mode == 'resign':
        ExploitResign(f, os.path.basename(f).replace('.apk', '-resigned.apk')).exploit()
      elif mode == 'unsign':
        ExploitUnsign(f, os.path.basename(f).replace('.apk', '-unsigned.apk')).exploit()
      elif mode == 'enable-debug':
        ExploitEnableDebug(f, os.path.basename(f).replace('.apk', '-debuggable.apk')).exploit()
      elif mode == 'enable-backup':
        ExploitEnableBackup(f, os.path.basename(f).replace('.apk', '-backupable.apk')).exploit()
      elif mode == 'disable-pinning':
        ExploitDisablePinning(f, os.path.basename(f).replace('.apk', '-unpinned.apk')).exploit()
    return 0

class ExploitUnsign:
  _unsigner: Unsigner
  def __init__(self, apk: str, out: str) -> None:
    from trueseeing.core.sign import Unsigner
    self._unsigner = Unsigner(apk, out)

  def exploit(self) -> None:
    self._unsigner.unsign()

class ExploitResign:
  _resigner: Resigner
  def __init__(self, apk: str, out: str) -> None:
    from trueseeing.core.sign import Resigner
    self._resigner = Resigner(apk, out)

  def exploit(self) -> None:
    self._resigner.resign()

class ExploitEnableDebug:
  _patcher: Patcher
  def __init__(self, apk: str, out: str) -> None:
    from trueseeing.core.patch import Patcher
    self._patcher = Patcher(apk, out)

  def exploit(self) -> None:
    self._patcher.apply(self)

  def apply(self, context: Context) -> None:
    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(context.manifest_as_xml(manifest))

class ExploitEnableBackup:
  _patcher: Patcher
  def __init__(self, apk: str, out: str) -> None:
    from trueseeing.core.patch import Patcher
    self._patcher = Patcher(apk, out)

  def exploit(self) -> None:
    self._patcher.apply(self)

  def apply(self, context: Context) -> None:
    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(context.manifest_as_xml(manifest))

class ExploitDisablePinning:
  _patcher: Patcher
  def __init__(self, apk: str, out: str) -> None:
    from trueseeing.core.patch import Patcher
    self._patcher = Patcher(apk, out)

  def exploit(self) -> None:
    self._patcher.apply(self)

  def apply(self, context: Context) -> None:
    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(context.manifest_as_xml(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>
''')
