diff --git a/trueseeing/app/exploit.py b/trueseeing/app/exploit.py deleted file mode 100644 index 91d1ad5..0000000 --- a/trueseeing/app/exploit.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding: utf-8 -*- -# Trueseeing: Non-decompiling Android application vulnerability scanner -# Copyright (C) 2017-23 Takahiro Yoshimura -# -# 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 . - -from __future__ import annotations -from typing import TYPE_CHECKING - -import os - -if TYPE_CHECKING: - from typing import List - from trueseeing.core.context import Context - from trueseeing.core.patch import Patcher - from trueseeing.core.sign import Unsigner, Resigner - -class ExploitMode: - _files: List[str] - def __init__(self, files: List[str]) -> None: - self._files = files - - async def invoke(self, mode: str, no_cache_mode: bool = False) -> int: - for f in self._files: - try: - if mode == 'resign': - await ExploitResign(f, os.path.basename(f).replace('.apk', '-resigned.apk')).exploit() - elif mode == 'unsign': - await ExploitUnsign(f, os.path.basename(f).replace('.apk', '-unsigned.apk')).exploit() - elif mode == 'enable-debug': - await ExploitEnableDebug(f, os.path.basename(f).replace('.apk', '-debuggable.apk')).exploit() - elif mode == 'enable-backup': - await ExploitEnableBackup(f, os.path.basename(f).replace('.apk', '-backupable.apk')).exploit() - elif mode == 'disable-pinning': - await ExploitDisablePinning(f, os.path.basename(f).replace('.apk', '-unpinned.apk')).exploit() - finally: - if no_cache_mode: - from trueseeing.core.context import Context - Context(f, []).remove() - - 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) - - async def exploit(self) -> None: - await 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) - - async def exploit(self) -> None: - await 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) - - async def exploit(self) -> None: - await 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" - context.store().query().patch_put(path='AndroidManifest.xml', blob=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) - - async def exploit(self) -> None: - await 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" - context.store().query().patch_put(path='AndroidManifest.xml', blob=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) - - async def exploit(self) -> None: - await self._patcher.apply(self) - - def apply(self, context: Context) -> None: - import lxml.etree as ET - 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 context.store().db as c: - from trueseeing.core.literalquery import Query - query = Query(c=c) - - query.patch_put(path='AndroidManifest.xml', blob=context.manifest_as_xml(manifest)) - query.patch_put(path='resources/package_1/res/xml/network_security_config.xml', blob=b'''\ - - - - - - - - - -''') - root = query.file_get_xml('resources/package_1/res/values/public.xml') - assert root is not None - if root.xpath('./public[@type="xml"]'): - maxid = max(int(e.attrib["id"], 16) for e in root.xpath('./public[@type="xml"]')) - n = ET.SubElement(root, 'public') - n.attrib['id'] = f'0x{maxid+1:08x}' - n.attrib['type'] = 'xml' - n.attrib['name'] = 'network_security_config' - else: - maxid = (max(int(e.attrib["id"], 16) for e in root.xpath('./public')) & 0xffff0000) - n = ET.SubElement(root, 'public') - n.attrib['id'] = f'0x{maxid+0x10000:08x}' - n.attrib['type'] = 'xml' - n.attrib['name'] = 'network_security_config' - query.patch_put(path='resources/package_1/res/values/public.xml', blob=ET.tostring(root)) diff --git a/trueseeing/app/fingerprint.py b/trueseeing/app/fingerprint.py deleted file mode 100644 index c73e866..0000000 --- a/trueseeing/app/fingerprint.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# Trueseeing: Non-decompiling Android application vulnerability scanner -# Copyright (C) 2017-23 Takahiro Yoshimura -# -# 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 . -from __future__ import annotations -from typing import TYPE_CHECKING - -from trueseeing.core.ui import ui - -if TYPE_CHECKING: - from typing import List - -class FingerprintMode: - _files: List[str] - def __init__(self, files: List[str]) -> None: - self._files = files - - async def invoke(self) -> int: - from trueseeing.core.context import Context - for f in self._files: - ui.stdout(f'{f}: {Context(f, []).fingerprint_of()}') - return 0 diff --git a/trueseeing/app/grab.py b/trueseeing/app/grab.py deleted file mode 100644 index 4d4e3c5..0000000 --- a/trueseeing/app/grab.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# Trueseeing: Non-decompiling Android application vulnerability scanner -# Copyright (C) 2017-23 Takahiro Yoshimura -# -# 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 . - -from __future__ import annotations -from typing import TYPE_CHECKING - -import re -import os -from trueseeing.core.tools import try_invoke, invoke, list_async -from trueseeing.core.ui import ui - -if TYPE_CHECKING: - from typing import List, Iterable, TypeVar, Tuple, AsyncIterable - T = TypeVar('T') - -FALLBACK_VERSION = 8.0 - -class GrabMode: - _packages: List[str] - def __init__(self, packages: List[str]) -> None: - self._packages = packages - - async def invoke(self) -> int: - if os.environ.get('TS2_IN_DOCKER'): - ui.fatal('grab mode in docker is not supported yet') - - if self._packages: - for pkg in self._packages: - if await Grab(pkg).exploit(): - ui.info(f'package saved: {pkg}.apk') - return 0 - else: - ui.fatal('package not found') - else: - ui.info('listing packages') - for p in sorted(await list_async(Grab.get_package_list())): - ui.stdout(p) - return 0 - -class Grab: - _target: str - def __init__(self, target: str) -> None: - self._target = target - - async def exploit(self) -> bool: - async for from_, to_ in self._path_from(self._target): - out = await try_invoke(f"adb pull {from_} {to_} 2>/dev/null") - if out is None: - out = await try_invoke(f"adb shell 'cat {from_} 2>/dev/null' > {to_}") - if out is not None and os.path.getsize(to_) > 0: - return True - else: - return False - return True - - @classmethod - async def get_package_list(cls) -> AsyncIterable[str]: - out = await invoke("adb shell pm list packages") - for l in filter(None, out.split('\n')): - yield l.replace('package:', '') - - @classmethod - async def _path_from(cls, package: str) -> AsyncIterable[Tuple[str, str]]: - version = await cls._version_of_default_device() - if version >= 8.0: - async for t in cls._path_from_dump(package): - yield t - elif version >= 4.4: - for t in cls._path_from_multidex(package): - yield t - else: - for t in cls._path_from_premultidex(package): - yield t - - @classmethod - def _path_from_premultidex(cls, package: str) -> Iterable[Tuple[str, str]]: - for i in range(1, 16): - yield f'/data/app/{package}-{i}.apk', f'{package}.apk' - - @classmethod - def _path_from_multidex(cls, package: str) -> Iterable[Tuple[str, str]]: - for i in range(1, 16): - yield f'/data/app/{package}-{i}/base.apk', f'{package}.apk' - - @classmethod - async def _path_from_dump(cls, package: str) -> AsyncIterable[Tuple[str, str]]: - out = await invoke(f'adb shell pm dump "{package}"') - m = re.search(f'codePath=(/data/app/.*{package}-.+)', out) - if m: - yield os.path.join(m.group(1), 'base.apk'), f'{package}.apk' - else: - raise RuntimeError('pm dump does not return codePath') - - @classmethod - async def _version_of_default_device(cls) -> float: - out = await try_invoke("adb shell cat /system/build.prop") - if out is None: - return FALLBACK_VERSION - m = re.search(r'ro.build.version.release=(.+?)', out) - if m: - try: - return float(m.group(1)) - except ValueError: - return FALLBACK_VERSION - else: - return FALLBACK_VERSION diff --git a/trueseeing/app/patch.py b/trueseeing/app/patch.py deleted file mode 100644 index 94227fc..0000000 --- a/trueseeing/app/patch.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -# Trueseeing: Non-decompiling Android application vulnerability scanner -# Copyright (C) 2017-23 Takahiro Yoshimura -# -# 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 . -from __future__ import annotations -from typing import TYPE_CHECKING - -import os - -if TYPE_CHECKING: - from typing import List - from trueseeing.core.context import Context - -class PatchMode: - _files: List[str] - def __init__(self, files: List[str]): - self._files = files - - async def invoke(self, mode: str, no_cache_mode: bool = False) -> int: - from trueseeing.core.patch import Patcher - for f in self._files: - try: - if mode == 'all': - await Patcher(f, os.path.basename(f).replace('.apk', '-patched.apk')).apply_multi([ - PatchDebuggable(), - PatchBackupable(), - PatchLoggers() - ]) - finally: - if no_cache_mode: - from trueseeing.core.context import Context - Context(f, []).remove() - - return 0 - -class PatchDebuggable: - def apply(self, context: Context) -> None: - manifest = context.parsed_manifest(patched=True) - for e in manifest.xpath('.//application'): - e.attrib['{http://schemas.android.com/apk/res/android}debuggable'] = "false" - context.store().query().patch_put(path='AndroidManifest.xml', blob=context.manifest_as_xml(manifest)) - -class PatchBackupable: - def apply(self, context: Context) -> None: - manifest = context.parsed_manifest(patched=True) - for e in manifest.xpath('.//application'): - e.attrib['{http://schemas.android.com/apk/res/android}allowBackup'] = "false" - context.store().query().patch_put(path='AndroidManifest.xml', blob=context.manifest_as_xml(manifest)) - -class PatchLoggers: - def apply(self, context: Context) -> None: - import re - with context.store().db as c: - from trueseeing.core.literalquery import Query - query = Query(c=c) - for fn, content in query.file_enum('smali/%.smali'): - stage0 = re.sub(rb'^.*?invoke-static.*?Landroid/util/Log;->.*?\(.*?$', b'', content, flags=re.MULTILINE) - stage1 = re.sub(rb'^.*?invoke-virtual.*?Ljava/io/Print(Writer|Stream);->.*?\(.*?$', b'', stage0, flags=re.MULTILINE) - if content != stage1: - query.patch_put(path=fn, blob=stage1) diff --git a/trueseeing/app/shell.py b/trueseeing/app/shell.py index 8602d34..b983f1f 100644 --- a/trueseeing/app/shell.py +++ b/trueseeing/app/shell.py @@ -28,7 +28,7 @@ from trueseeing.signature.base import Detector from trueseeing.core.report import ReportFormat - OpMode = Optional[Literal['grab', 'exploit', 'patch', 'fingerprint', 'scan', 'inspect', 'batch']] + OpMode = Optional[Literal['scan', 'inspect', 'batch']] class Signatures: content: Dict[str, Type[Detector]] @@ -122,7 +122,7 @@ def _help(cls) -> str: Scan mode: --scan-sigs=,.. Select signatures (use --help-signatures to list signatures) --scan-exclude= Excluding packages matching pattern - -o/--scan-output= Report filename ("-" for stdout) + --scan-output= Report filename ("-" for stdout) --scan-report=html|json Report format (html: HTML (default), json: JSON) --scan-max-graph-size= Set max graph size --scan-no-cache Do not keep codebase cache @@ -158,8 +158,6 @@ def invoke(self) -> int: log_level = ui.INFO signature_selected = sigs.default().copy() mode: OpMode = None - exploit = '' - patch = '' cmdlines = [] no_cache_mode = False update_cache_mode = False @@ -168,16 +166,14 @@ def invoke(self) -> int: exclude_packages: List[str] = [] opts, files = getopt.getopt(sys.argv[1:], 'c:i:do:qW:', - ['debug', 'exploit-resign', 'exploit-unsign', 'exploit-enable-debug', 'exploit-enable-backup', - 'exploit-disable-pinning', 'fingerprint', 'grab', 'help', 'help-signatures', - 'output=', 'format=', 'version', 'patch-all', 'exclude=', 'update-cache', 'no-cache', 'inspect', 'max-graph-size=', + ['debug', + 'help', 'help-signatures', + 'version', 'inspect', 'scan', 'scan-sigs=', 'scan-output=', 'scan-report=', 'scan-exclude=', 'scan-update-cache', 'scan-no-cache', 'scan-max-graph-size=']) for o, a in opts: if o in ['-d', '--debug']: log_level = ui.DEBUG - if o == '--output': - self._deprecated(f'{o} is deprecated (use --scan-output)') - if o in ['-o', '--output']: + if o in ['--scan-output']: output_filename = a if o in ['-q']: mode = 'batch' @@ -190,72 +186,26 @@ def invoke(self) -> int: except OSError as e: ui.fatal(f'cannot open script file: {e}') - if o in ['-W']: - self._deprecated('-W/-Wno- is deprecated (use --scan-sigs)') - if a.startswith('no-'): - signature_selected.difference_update(sigs.selected_on(a[3:])) - else: - signature_selected.update(sigs.selected_on(a)) if o in ['--scan-sigs']: for s in a.split(','): if s.startswith('no-'): signature_selected.difference_update(sigs.selected_on(s[3:])) else: signature_selected.update(sigs.selected_on(s)) - if o in ['--exploit-resign']: - self._deprecated(f'{o} is deprecated') - mode = 'exploit' - exploit = 'resign' - if o in ['--exploit-unsign']: - self._deprecated(f'{o} is deprecated') - mode = 'exploit' - exploit = 'unsign' - if o in ['--exploit-enable-debug']: - self._deprecated(f'{o} is deprecated (try xd in inspect mode)') - mode = 'exploit' - exploit = 'enable-debug' - if o in ['--exploit-enable-backup']: - self._deprecated(f'{o} is deprecated (try xb in inspect mode)') - mode = 'exploit' - exploit = 'enable-backup' - if o in ['--exploit-disable-pinning']: - self._deprecated(f'{o} is deprecated (try xu in inspect mode)') - mode = 'exploit' - exploit = 'disable-pinning' - if o == '--exclude': - self._deprecated(f'{o} is deprecated (use --scan-exclude)') - if o in ['--scan-exclude', '--exclude']: + if o in ['--scan-exclude']: exclude_packages.append(a) - if o in ['--patch-all']: - self._deprecated(f'{o} is deprecated') - mode = 'patch' - patch = 'all' - if o in ['--grab']: - self._deprecated(f'{o} is deprecated') - mode = 'grab' - if o in ['--fingerprint']: - self._deprecated(f'{o} is deprecated (try i in inspect mode)') - mode = 'fingerprint' if o in ['--inspect']: - mode = 'inspect' + self._deprecated(f'{o} is deprecated; ignored as default') if o in ['--scan']: mode = 'scan' - if o in ['--update-cache']: - self._deprecated(f'{o} is deprecated (use --scan-update-cache)') - if o in ['--update-cache', '--scan-update-cache']: + if o in ['--scan-update-cache']: update_cache_mode = True - if o in ['--no-cache']: - self._deprecated(f'{o} is deprecated (use --scan-no-cache)') - if o in ['--no-cache', '--scan-no-cache']: + if o in ['--scan-no-cache']: no_cache_mode = True - if o == '--max-graph-size': - self._deprecated(f'{o} is deprecated (use --scan-max-graph-size)') - if o in ['--max-graph-size', '--scan-max-graph-size']: + if o in ['--scan-max-graph-size']: from trueseeing.core.flow.data import DataFlows DataFlows.set_max_graph_size(int(a)) - if o == '--format': - self._deprecated(f'{o} is deprecated (use --scan-report)') - if o in ['--scan-report', '--format']: + if o in ['--scan-report']: # NB: should check "a" conforms to the literal type, ReportFormat if a in ['html', 'json']: format = a # type: ignore[assignment] @@ -276,53 +226,34 @@ def invoke(self) -> int: if not mode: mode = 'inspect' - if mode == 'grab': - from trueseeing.app.grab import GrabMode - return self._launch(GrabMode(packages=files).invoke()) + if not files: + ui.stderr(self._help()) + return 2 + + if mode in ['inspect', 'batch']: + if len(files) > 1: + ui.fatal(f"{mode} mode accepts at most only one target file") + from trueseeing.app.inspect import InspectMode + InspectMode().do( + files[0] if files else '', + signatures=sigs, + batch=True if mode == 'batch' else False, + cmdlines=cmdlines + ) + elif mode == 'scan': + if len(files) > 1: + self._deprecated('specifying multiple files is deprecated') + from trueseeing.app.scan import ScanMode + return self._launch(ScanMode(files).invoke( + ci_mode=format, + outfile=output_filename, + signatures=[v for k, v in sigs.content.items() if k in signature_selected], + exclude_packages=exclude_packages, + no_cache_mode=no_cache_mode, + update_cache_mode=update_cache_mode, + )) else: - if not files: - ui.stderr(self._help()) - return 2 - if mode in ['inspect', 'batch']: - if len(files) > 1: - ui.fatal(f"{mode} mode accepts at most only one target file") - from trueseeing.app.inspect import InspectMode - InspectMode().do( - files[0] if files else '', - signatures=sigs, - batch=True if mode == 'batch' else False, - cmdlines=cmdlines - ) - else: - if len(files) > 1: - self._deprecated('specifying multiple files is deprecated') - if mode == 'exploit': - from trueseeing.app.exploit import ExploitMode - return self._launch(ExploitMode(files).invoke( - exploit, - no_cache_mode=no_cache_mode - )) - elif mode == 'patch': - from trueseeing.app.patch import PatchMode - return self._launch(PatchMode(files).invoke( - patch, - no_cache_mode=no_cache_mode - )) - elif mode == 'fingerprint': - from trueseeing.app.fingerprint import FingerprintMode - return self._launch(FingerprintMode(files).invoke()) - elif mode == 'scan': - from trueseeing.app.scan import ScanMode - return self._launch(ScanMode(files).invoke( - ci_mode=format, - outfile=output_filename, - signatures=[v for k, v in sigs.content.items() if k in signature_selected], - exclude_packages=exclude_packages, - no_cache_mode=no_cache_mode, - update_cache_mode=update_cache_mode, - )) - else: - assert False, f'unknown mode: {mode}' + assert False, f'unknown mode: {mode}' def entry() -> None: from trueseeing.core.exc import FatalError diff --git a/trueseeing/core/patch.py b/trueseeing/core/patch.py deleted file mode 100644 index f99d192..0000000 --- a/trueseeing/core/patch.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -# Trueseeing: Non-decompiling Android application vulnerability scanner -# Copyright (C) 2017-23 Takahiro Yoshimura -# -# 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 . - -from __future__ import annotations -from typing import TYPE_CHECKING - -import os -from shutil import copyfile - -from trueseeing.core.sign import SigningKey -from trueseeing.core.context import Context -from trueseeing.core.ui import ui - -if TYPE_CHECKING: - from typing import List, Protocol - - class Patch(Protocol): - def apply(self, context: Context) -> None: ... - -class Patcher: - def __init__(self, apk: str, out: str) -> None: - self._path = os.path.realpath(apk) - self._outpath = os.path.realpath(out) - - async def apply(self, patch: Patch) -> None: - return await self.apply_multi([patch]) - - async def apply_multi(self, patches: List[Patch]) -> None: - context = Context(self._path, []) - await context.analyze() - ui.info(f"{self._path} -> {context.wd}") - for p in patches: - p.apply(context) - - await self._build(context) - - async def _build(self, context: Context) -> None: - from tempfile import TemporaryDirectory - from trueseeing.core.literalquery import Query - - # XXX insecure - with TemporaryDirectory() as d: - with context.store().db as c: - query = Query(c=c) - cwd = os.getcwd() - try: - os.chdir(d) - os.makedirs('files') - os.chdir('files') - for path, blob in query.file_enum(pat=None): - dirname = os.path.dirname(path) - if dirname: - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, 'wb') as f: - f.write(blob) - for path, blob in query.patch_enum(pat=None): - dirname = os.path.dirname(path) - if dirname: - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, 'wb') as f: - f.write(blob) - query.patch_clear() - c.commit() - finally: - os.chdir(cwd) - - from trueseeing.core.tools import invoke_passthru, toolchains - with toolchains() as tc: - await invoke_passthru('(cd {root} && java -jar {apkeditor} b -i files -o patched.apk && java -jar {apksigner} sign --ks {keystore} --ks-pass pass:android patched.apk && cp -a patched.apk {outpath})'.format( - root=d, - apkeditor=tc['apkeditor'], - apksigner=tc['apksigner'], - keystore=await SigningKey().key(), - outpath=self._outpath, - )) - copyfile(os.path.join(d, 'patched.apk'), self._outpath) - copyfile(os.path.join(d, 'patched.apk.idsig'), self._outpath + '.idsig')