[
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# r0capture\n\n安卓应用层抓包通杀脚本\n\n## 简介\n\n- 仅限安卓平台，测试安卓7、8、9、10、11、12、13、14 、15、16可用 ；\n- 无视所有证书校验或绑定，不用考虑任何证书的事情；\n- 通杀TCP/IP四层模型中的应用层中的全部协议；\n- 通杀协议包括：Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本；\n- 通杀所有应用层框架，包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等；\n- 无视加固，不管是整体壳还是二代壳或VMP，不用考虑加固的事情；\n- 如果有抓不到的情况欢迎提issue，或者直接加vx：r0ysue，进行反馈~\n\n### March.2026更新frida17支持 \n推荐搭配：frida17/安卓16     frida16.5.2/安卓14      frida15.2.2/安卓12\n\n### June.18th 2023 update：测试Pixel4/安卓13/KernelSU/Frida16 功能工作正常 正常抓包 导出证书\n\n### January.14th 2021 update：增加几个辅助功能\n\n- 增加App收发包函数定位功能\n- 增加App客户端证书导出功能\n- 新增host连接方式“-H”，用于Frida-server监听在非标准端口时的连接\n\n## 用法\n\n- 推荐环境：[https://github.com/r0ysue/AndroidSecurityStudy/blob/master/FRIDA/A01/README.md](https://github.com/r0ysue/AndroidSecurityStudy/blob/master/FRIDA/A01/README.md)\n\n切记仅限安卓平台7、8、9、10、11 可用 ，禁止使用模拟器。\n\n- Spawn 模式：\n\n`$ python3 r0capture.py -U -f com.coolapk.market -v`\n\n- Attach 模式，抓包内容保存成pcap文件供后续分析： \n\n`$ python3 r0capture.py -U 酷安 -v -p iqiyi.pcap` \n\n建议使用`Attach`模式，从感兴趣的地方开始抓包，并且保存成`pcap`文件，供后续使用Wireshark进行分析。\n> 老版本Frida使用包名，新版本Frida使用APP名。APP名必须是点开app后，frida-ps -U显示的那个app名字。\n\n![](pic/Sample.PNG)\n\n- 收发包函数定位：`Spawn`和`attach`模式均默认开启；\n\n> 可以使用`python r0capture.py -U -f cn.soulapp.android -v  >> soul3.txt`这样的命令将输出重定向至txt文件中稍后过滤内容\n\n![](pic/locator.png)\n\n- 客户端证书导出功能：默认开启；必须以Spawm模式运行；\n\n> 运行脚本之前必须手动给App加上存储卡读写权限；\n\n> 并不是所有App都部署了服务器验证客户端的机制，只有配置了的才会在Apk中包含客户端证书\n\n> 导出后的证书位于/sdcard/Download/包名xxx.p12路径，导出多次，每一份均可用，密码默认为：r0ysue，推荐使用[keystore-explorer](http://keystore-explorer.org/)打开查看证书。\n\n![](pic/clientcer.png)\n\n- 新增host连接方式“-H”，用于Frida-server监听在非标准端口时的连接。有些App会检测Frida标准端口，因此frida-server开在非标准端口可以绕过检测。\n\n![](pic/difport.png)\n\n## 感谢[爱吃菠菜](https://bbs.pediy.com/user-760871.htm)巨巨总结的本项目知识点\n\n![](pic/summary1.jpg)\n![](pic/summary2.jpg)\n\n\nPS：\n\n> 这个项目基于[frida_ssl_logger](https://github.com/BigFaceCat2017/frida_ssl_logger)，之所以换个名字，只是侧重点不同。 原项目的侧重点在于抓ssl和跨平台，本项目的侧重点是抓到所有的包。\n\n> 局限：部分开发实力过强的大厂或框架，采用的是自身的SSL框架，比如WebView、小程序或Flutter，这部分目前暂未支持。部分融合App本质上已经不属于安卓App，没有使用安卓系统的框架，无法支持。当然这部分App也是少数。暂不支持HTTP/2、或HTTP/3，该部分API在安卓系统上暂未普及或布署，为App自带，无法进行通用hook。各种模拟器架构、实现、环境较为复杂，建议珍爱生命、使用真机。暂未添加多进程支持，比如:service或:push等子进程，可以使用Frida的Child-gating来支持一下。支持多进程之后要考虑pcap文件的写入锁问题，可以用frida-tool的Reactor线程锁来支持一下。\n\n## 以下是原项目的简介：\n\n[https://github.com/BigFaceCat2017/frida_ssl_logger](https://github.com/BigFaceCat2017/frida_ssl_logger)\n\n### frida_ssl_logger\nssl_logger based on frida\nfor from https://github.com/google/ssl_logger\n\n### 修改内容\n1. 优化了frida的JS脚本，修复了在新版frida上的语法错误；\n2. 调整JS脚本，使其适配iOS和macOS，同时也兼容了Android；\n3. 增加了更多的选项，使其能在多种情况下使用；\n\n### 安装依赖\n```\nPython版本>=3.6\npip install loguru\npip install click\n```\n### Usage\n  ```shell\n    python3 ./ssl_logger.py  -U -f com.bfc.mm\n    python3 ./ssl_logger.py -v  -p test.pcap  6666\n  ````\n\n"
  },
  {
    "path": "myhexdump.py",
    "content": "# -*- coding: utf-8 -*-\n# !/usr/bin/env python\n# -*- coding: latin-1 -*-\n\n# <-- removing this magic comment breaks Python 3.4 on Windows\n\"\"\"\n1. Dump binary data to the following text format:\n\n00000000: 00 00 00 5B 68 65 78 64  75 6D 70 5D 00 00 00 00  ...[hexdump]....\n00000010: 00 11 22 33 44 55 66 77  88 99 AA BB CC DD EE FF  ..\"3DUfw........\n\nIt is similar to the one used by:\nScapy\n00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00  ...[hexdump]....\n00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF  ..\"3DUfw........\n\nFar Manager\n000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00     [hexdump]\n000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 AA BB CC DD EE FF   ?\"3DUfwª»ÌÝîÿ\n\n\n2. Restore binary data from the formats above as well\n   as from less exotic strings of raw hex\n\n\"\"\"\n\n__version__ = '3.3'\n__author__ = 'anatoly techtonik <techtonik@gmail.com>'\n__license__ = 'Public Domain'\n\n__history__ = \\\n    \"\"\"\n    3.3 (2015-01-22)\n     * accept input from sys.stdin if \"-\" is specified\n       for both dump and restore (issue #1)\n     * new normalize_py() helper to set sys.stdout to\n       binary mode on Windows\n    \n    3.2 (2015-07-02)\n     * hexdump is now packaged as .zip on all platforms\n       (on Linux created archive was tar.gz)\n     * .zip is executable! try `python hexdump-3.2.zip`\n     * dump() now accepts configurable separator, patch\n       by Ian Land (PR #3)\n    \n    3.1 (2014-10-20)\n     * implemented workaround against mysterious coding\n       issue with Python 3 (see revision 51302cf)\n     * fix Python 3 installs for systems where UTF-8 is\n       not default (Windows), thanks to George Schizas\n       (the problem was caused by reading of README.txt)\n    \n    3.0 (2014-09-07)\n     * remove unused int2byte() helper\n     * add dehex(text) helper to convert hex string\n       to binary data\n     * add 'size' argument to dump() helper to specify\n       length of chunks\n    \n    2.0 (2014-02-02)\n     * add --restore option to command line mode to get\n       binary data back from hex dump\n     * support saving test output with `--test logfile`\n     * restore() from hex strings without spaces\n     * restore() now raises TypeError if input data is\n       not string\n     * hexdump() and dumpgen() now don't return unicode\n       strings in Python 2.x when generator is requested\n    \n    1.0 (2013-12-30)\n     * length of address is reduced from 10 to 8\n     * hexdump() got new 'result' keyword argument, it\n       can be either 'print', 'generator' or 'return'\n     * actual dumping logic is now in new dumpgen()\n       generator function\n     * new dump(binary) function that takes binary data\n       and returns string like \"66 6F 72 6D 61 74\"\n     * new genchunks(mixed, size) function that chunks\n       both sequences and file like objects\n    \n    0.5 (2013-06-10)\n     * hexdump is now also a command line utility (no\n       restore yet)\n    \n    0.4 (2013-06-09)\n     * fix installation with Python 3 for non English\n       versions of Windows, thanks to George Schizas\n    \n    0.3 (2013-04-29)\n     * fully Python 3 compatible\n    \n    0.2 (2013-04-28)\n     * restore() to recover binary data from a hex dump in\n       native, Far Manager and Scapy text formats (others\n       might work as well)\n     * restore() is Python 3 compatible\n    \n    0.1 (2013-04-28)\n     * working hexdump() function for Python 2\n    \"\"\"\n\nimport binascii  # binascii is required for Python 3\nimport sys\n\n# --- constants\nPY3K = sys.version_info >= (3, 0)\n\n\n# --- workaround against Python consistency issues\ndef normalize_py():\n    ''' Problem 001 - sys.stdout in Python is by default opened in\n        text mode, and writes to this stdout produce corrupted binary\n        data on Windows\n\n            python -c \"import sys; sys.stdout.write('_\\n_')\" > file\n            python -c \"print(repr(open('file', 'rb').read()))\"\n    '''\n    if sys.platform == \"win32\":\n        # set sys.stdout to binary mode on Windows\n        import os, msvcrt\n        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)\n\n\n# --- - chunking helpers\ndef chunks(seq, size):\n    '''Generator that cuts sequence (bytes, memoryview, etc.)\n       into chunks of given size. If `seq` length is not multiply\n       of `size`, the lengh of the last chunk returned will be\n       less than requested.\n\n       >>> list( chunks([1,2,3,4,5,6,7], 3) )\n       [[1, 2, 3], [4, 5, 6], [7]]\n    '''\n    d, m = divmod(len(seq), size)\n    for i in range(d):\n        yield seq[i * size:(i + 1) * size]\n    if m:\n        yield seq[d * size:]\n\n\ndef chunkread(f, size):\n    '''Generator that reads from file like object. May return less\n       data than requested on the last read.'''\n    c = f.read(size)\n    while len(c):\n        yield c\n        c = f.read(size)\n\n\ndef genchunks(mixed, size):\n    '''Generator to chunk binary sequences or file like objects.\n       The size of the last chunk returned may be less than\n       requested.'''\n    if hasattr(mixed, 'read'):\n        return chunkread(mixed, size)\n    else:\n        return chunks(mixed, size)\n\n\n# --- - /chunking helpers\n\n\ndef dehex(hextext):\n    \"\"\"\n    Convert from hex string to binary data stripping\n    whitespaces from `hextext` if necessary.\n    \"\"\"\n    if PY3K:\n        return bytes.fromhex(hextext)\n    else:\n        hextext = \"\".join(hextext.split())\n        return hextext.decode('hex')\n\n\ndef dump(binary, size=2, sep=' '):\n    '''\n    Convert binary data (bytes in Python 3 and str in\n    Python 2) to hex string like '00 DE AD BE EF'.\n    `size` argument specifies length of text chunks\n    and `sep` sets chunk separator.\n    '''\n    hexstr = binascii.hexlify(binary)\n    if PY3K:\n        hexstr = hexstr.decode('ascii')\n    return sep.join(chunks(hexstr.upper(), size))\n\n\ndef dumpgen(data, only_str):\n    '''\n    Generator that produces strings:\n\n    '00000000: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................'\n    '''\n    generator = genchunks(data, 16)\n    for addr, d in enumerate(generator):\n        line = \"\"\n        if not only_str:\n            # 00000000:\n            line = '%08X: ' % (addr * 16)\n            # 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00\n            dumpstr = dump(d)\n            line += dumpstr[:8 * 3]\n            if len(d) > 8:  # insert separator if needed\n                line += ' ' + dumpstr[8 * 3:]\n            # ................\n            # calculate indentation, which may be different for the last line\n            pad = 2\n            if len(d) < 16:\n                pad += 3 * (16 - len(d))\n            if len(d) <= 8:\n                pad += 1\n            line += ' ' * pad\n\n        for byte in d:\n            # printable ASCII range 0x20 to 0x7E\n            if not PY3K:\n                byte = ord(byte)\n            if 0x20 <= byte <= 0x7E:\n                line += chr(byte)\n            else:\n                line += '.'\n        yield line\n\n\ndef hexdump(data, result='print', only_str=False):\n    '''\n    Transform binary data to the hex dump text format:\n\n    00000000: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................\n\n      [x] data argument as a binary string\n      [x] data argument as a file like object\n\n    Returns result depending on the `result` argument:\n      'print'     - prints line by line\n      'return'    - returns single string\n      'generator' - returns generator that produces lines\n    '''\n    if PY3K and type(data) == str:\n        raise TypeError('Abstract unicode data (expected bytes sequence)')\n\n    gen = dumpgen(data, only_str=only_str)\n    if result == 'generator':\n        return gen\n    elif result == 'return':\n        return '\\n'.join(gen)\n    elif result == 'print':\n        for line in gen:\n            print(line)\n    else:\n        raise ValueError('Unknown value of `result` argument')\n\n\ndef restore(dump):\n    '''\n    Restore binary data from a hex dump.\n      [x] dump argument as a string\n      [ ] dump argument as a line iterator\n\n    Supported formats:\n      [x] hexdump.hexdump\n      [x] Scapy\n      [x] Far Manager\n    '''\n    minhexwidth = 2 * 16  # minimal width of the hex part - 00000... style\n    bytehexwidth = 3 * 16 - 1  # min width for a bytewise dump - 00 00 ... style\n\n    result = bytes() if PY3K else ''\n    if type(dump) != str:\n        raise TypeError('Invalid data for restore')\n\n    text = dump.strip()  # ignore surrounding empty lines\n    for line in text.split('\\n'):\n        # strip address part\n        addrend = line.find(':')\n        if 0 < addrend < minhexwidth:  # : is not in ascii part\n            line = line[addrend + 1:]\n        line = line.lstrip()\n        # check dump type\n        if line[2] == ' ':  # 00 00 00 ...  type of dump\n            # check separator\n            sepstart = (2 + 1) * 7 + 2  # ('00'+' ')*7+'00'\n            sep = line[sepstart:sepstart + 3]\n            if sep[:2] == '  ' and sep[2:] != ' ':  # ...00 00  00 00...\n                hexdata = line[:bytehexwidth + 1]\n            elif sep[2:] == ' ':  # ...00 00 | 00 00...  - Far Manager\n                hexdata = line[:sepstart] + line[sepstart + 3:bytehexwidth + 2]\n            else:  # ...00 00 00 00... - Scapy, no separator\n                hexdata = line[:bytehexwidth]\n            line = hexdata\n        result += dehex(line)\n    return result\n\n\ndef runtest(logfile=None):\n    '''Run hexdump tests. Requires hexfile.bin to be in the same\n       directory as hexdump.py itself'''\n\n    class TeeOutput(object):\n        def __init__(self, stream1, stream2):\n            self.outputs = [stream1, stream2]\n\n        # -- methods from sys.stdout / sys.stderr\n        def write(self, data):\n            for stream in self.outputs:\n                if PY3K:\n                    if 'b' in stream.mode:\n                        data = data.encode('utf-8')\n                stream.write(data)\n                stream.flush()\n\n        def tell(self):\n            raise IOError\n\n        def flush(self):\n            for stream in self.outputs:\n                stream.flush()\n        # --/ sys.stdout\n\n    if logfile:\n        openlog = open(logfile, 'wb')\n        # copy stdout and stderr streams to log file\n        savedstd = sys.stderr, sys.stdout\n        sys.stderr = TeeOutput(sys.stderr, openlog)\n        sys.stdout = TeeOutput(sys.stdout, openlog)\n\n    def echo(msg, linefeed=True):\n        sys.stdout.write(msg)\n        if linefeed:\n            sys.stdout.write('\\n')\n\n    expected = '''\\\n00000000: 00 00 00 5B 68 65 78 64  75 6D 70 5D 00 00 00 00  ...[hexdump]....\n00000010: 00 11 22 33 44 55 66 77  88 99 0A BB CC DD EE FF  ..\"3DUfw........\\\n'''\n\n    # get path to hexfile.bin\n    # this doesn't work from .zip\n    #   import os.path as osp\n    #   hexfile = osp.dirname(osp.abspath(__file__)) + '/hexfile.bin'\n    # this doesn't work either\n    #   hexfile = osp.dirname(sys.modules[__name__].__file__) + '/hexfile.bin'\n    # this works\n    import pkgutil\n    bin = pkgutil.get_data('hexdump', 'data/hexfile.bin')\n\n    # varios length of input data\n    hexdump(b'zzzz' * 12)\n    hexdump(b'o' * 17)\n    hexdump(b'p' * 24)\n    hexdump(b'q' * 26)\n    # allowable character set filter\n    hexdump(b'line\\nfeed\\r\\ntest')\n    hexdump(b'\\x00\\x00\\x00\\x5B\\x68\\x65\\x78\\x64\\x75\\x6D\\x70\\x5D\\x00\\x00\\x00\\x00'\n            b'\\x00\\x11\\x22\\x33\\x44\\x55\\x66\\x77\\x88\\x99\\x0A\\xBB\\xCC\\xDD\\xEE\\xFF')\n    print('---')\n    # dumping file-like binary object to screen (default behavior)\n    hexdump(bin)\n    print('return output')\n    hexout = hexdump(bin, result='return')\n    assert hexout == expected, 'returned hex didn\\'t match'\n    print('return generator')\n    hexgen = hexdump(bin, result='generator')\n    assert next(hexgen) == expected.split('\\n')[0], 'hex generator 1 didn\\'t match'\n    assert next(hexgen) == expected.split('\\n')[1], 'hex generator 2 didn\\'t match'\n\n    # binary restore test\n    bindata = restore(\n        '''\n        00000000: 00 00 00 5B 68 65 78 64  75 6D 70 5D 00 00 00 00  ...[hexdump]....\n        00000010: 00 11 22 33 44 55 66 77  88 99 0A BB CC DD EE FF  ..\"3DUfw........\n        ''')\n    echo('restore check ', linefeed=False)\n    assert bin == bindata, 'restore check failed'\n    echo('passed')\n\n    far = \\\n        '''\n        000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00     [hexdump]\n        000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 0A BB CC DD EE FF   ?\"3DUfwª»ÌÝîÿ\n        '''\n    echo('restore far format ', linefeed=False)\n    assert bin == restore(far), 'far format check failed'\n    echo('passed')\n\n    scapy = '''\\\n00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00  ...[hexdump]....\n00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF  ..\"3DUfw........\n'''\n    echo('restore scapy format ', linefeed=False)\n    assert bin == restore(scapy), 'scapy format check failed'\n    echo('passed')\n\n    if not PY3K:\n        assert restore('5B68657864756D705D') == '[hexdump]', 'no space check failed'\n        assert dump('\\\\\\xa1\\xab\\x1e', sep='').lower() == '5ca1ab1e'\n    else:\n        assert restore('5B68657864756D705D') == b'[hexdump]', 'no space check failed'\n        assert dump(b'\\\\\\xa1\\xab\\x1e', sep='').lower() == '5ca1ab1e'\n\n    print('---[test file hexdumping]---')\n\n    import os\n    import tempfile\n    hexfile = tempfile.NamedTemporaryFile(delete=False)\n    try:\n        hexfile.write(bin)\n        hexfile.close()\n        hexdump(open(hexfile.name, 'rb'))\n    finally:\n        os.remove(hexfile.name)\n    if logfile:\n        sys.stderr, sys.stdout = savedstd\n        openlog.close()\n\n\ndef main():\n    from optparse import OptionParser\n    parser = OptionParser(usage='''\n  %prog [binfile|-]\n  %prog -r hexfile\n  %prog --test [logfile]''', version=__version__)\n    parser.add_option('-r', '--restore', action='store_true',\n                      help='restore binary from hex dump')\n    parser.add_option('--test', action='store_true', help='run hexdump sanity checks')\n\n    options, args = parser.parse_args()\n\n    if options.test:\n        if args:\n            runtest(logfile=args[0])\n        else:\n            runtest()\n    elif not args or len(args) > 1:\n        parser.print_help()\n        sys.exit(-1)\n    else:\n        ## dump file\n        if not options.restore:\n            # [x] memory effective dump\n            if args[0] == '-':\n                if not PY3K:\n                    hexdump(sys.stdin)\n                else:\n                    hexdump(sys.stdin.buffer)\n            else:\n                hexdump(open(args[0], 'rb'))\n\n        ## restore file\n        else:\n            # prepare input stream\n            if args[0] == '-':\n                instream = sys.stdin\n            else:\n                if PY3K:\n                    instream = open(args[0])\n                else:\n                    instream = open(args[0], 'rb')\n\n            # output stream\n            # [ ] memory efficient restore\n            if PY3K:\n                sys.stdout.buffer.write(restore(instream.read()))\n            else:\n                # Windows - binary mode for sys.stdout to prevent data corruption\n                normalize_py()\n                sys.stdout.write(restore(instream.read()))\n\n\nif __name__ == '__main__':\n    main()\n\n# [x] file restore from command line utility\n# [ ] write dump with LF on Windows for consistency\n# [ ] encoding param for hexdump()ing Python 3 str if anybody requests that\n\n# [ ] document chunking API\n# [ ] document hexdump API\n# [ ] blog about sys.stdout text mode problem on Windows\n"
  },
  {
    "path": "r0capture.py",
    "content": "# Copyright 2017 Google Inc. All Rights Reserved.\n\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Decrypts and logs a process's SSL traffic.\nHooks the functions SSL_read() and SSL_write() in a given process and logs the\ndecrypted data to the console and/or to a pcap file.\n  Typical usage example:\n  ssl_log(\"wget\", \"log.pcap\", True)\nDependencies:\n  frida (https://www.frida.re/):\n    sudo pip install frida\n  hexdump (https://bitbucket.org/techtonik/hexdump/) if using verbose output:\n    sudo pip install hexdump\n\"\"\"\n\n__author__ = \"geffner@google.com (Jason Geffner)\"\n__version__ = \"2.0\"\n\n\"\"\"\n# r0capture\n\nID: r0ysue \n\n安卓应用层抓包通杀脚本\n\nhttps://github.com/r0ysue/r0capture\n\n## 简介\n\n- 仅限安卓平台，测试安卓7、8、9、10 可用 ；\n- 无视所有证书校验或绑定，无视任何证书；\n- 通杀TCP/IP四层模型中的应用层中的全部协议；\n- 通杀协议包括：Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本；\n- 通杀所有应用层框架，包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等；\n\"\"\"\n\n# Windows版本需要安装库：\n# pip install 'win_inet_pton'\n# pip install hexdump\nimport argparse\nimport os\nimport pprint\nimport random\nimport signal\nimport socket\nimport struct\nimport sys\nimport time\nfrom pathlib import Path\n\nimport frida\nfrom loguru import logger\n\ntry:\n    if os.name == 'nt':\n        import win_inet_pton\nexcept ImportError:\n    # win_inet_pton import error\n    pass\n\ntry:\n    import myhexdump as hexdump # pylint: disable=g-import-not-at-top\nexcept ImportError:\n    pass\ntry:\n    from shutil import get_terminal_size as get_terminal_size\nexcept:\n    try:\n        from backports.shutil_get_terminal_size import get_terminal_size as get_terminal_size\n    except:\n        pass\n\ntry:\n    import click\nexcept:\n    class click:\n        @staticmethod\n        def secho(message=None, **kwargs):\n            print(message)\n\n        @staticmethod\n        def style(**kwargs):\n            raise Exception(\"unsupported style\")\nbanner = \"\"\"\n--------------------------------------------------------------------------------------------\n           .oooo.                                      .                                  \n          d8P'`Y8b                                   .o8                                  \noooo d8b 888    888  .ooooo.   .oooo.   oo.ooooo.  .o888oo oooo  oooo  oooo d8b  .ooooo.  \n`888\"\"8P 888    888 d88' `\"Y8 `P  )88b   888' `88b   888   `888  `888  `888\"\"8P d88' `88b \n 888     888    888 888        .oP\"888   888   888   888    888   888   888     888ooo888 \n 888     `88b  d88' 888   .o8 d8(  888   888   888   888 .  888   888   888     888    .o \nd888b     `Y8bd8P'  `Y8bod8P' `Y888\"\"8o  888bod8P'   \"888\"  `V88V\"V8P' d888b    `Y8bod8P' \n                                         888                                              \n                                        o888o                                                                                                                                       \n                    https://github.com/r0ysue/r0capture\n--------------------------------------------------------------------------------------------\\n\n\"\"\"\n\n\ndef show_banner():\n    colors = ['bright_red', 'bright_green', 'bright_blue', 'cyan', 'magenta']\n    try:\n        click.style('color test', fg='bright_red')\n    except:\n        colors = ['red', 'green', 'blue', 'cyan', 'magenta']\n    try:\n        columns = get_terminal_size().columns\n        if columns >= len(banner.splitlines()[1]):\n            for line in banner.splitlines():\n                click.secho(line, fg=random.choice(colors))\n    except:\n        pass\n\n\n# ssl_session[<SSL_SESSION id>] = (<bytes sent by client>,\n#                                  <bytes sent by server>)\nssl_sessions = {}\n\n\ndef ssl_log(process, pcap=None, host=False, verbose=False, isUsb=False, ssllib=\"\", isSpawn=True, wait=0):\n    \"\"\"Decrypts and logs a process's SSL traffic.\n    Hooks the functions SSL_read() and SSL_write() in a given process and logs\n    the decrypted data to the console and/or to a pcap file.\n    Args:\n      process: The target process's name (as a string) or process ID (as an int).\n      pcap: The file path to which the pcap file should be written.\n      verbose: If True, log the decrypted traffic to the console.\n    Raises:\n      NotImplementedError: Not running on a Linux or macOS system.\n    \"\"\"\n\n    # if platform.system() not in (\"Darwin\", \"Linux\"):\n    #   raise NotImplementedError(\"This function is only implemented for Linux and \"\n    #                             \"macOS systems.\")\n\n    def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,\n                 dst_addr, dst_port, data):\n        \"\"\"Writes the captured data to a pcap file.\n        Args:\n          pcap_file: The opened pcap file.\n          ssl_session_id: The SSL session ID for the communication.\n          function: The function that was intercepted (\"SSL_read\" or \"SSL_write\").\n          src_addr: The source address of the logged packet.\n          src_port: The source port of the logged packet.\n          dst_addr: The destination address of the logged packet.\n          dst_port: The destination port of the logged packet.\n          data: The decrypted packet data.\n        \"\"\"\n        t = time.time()\n\n        if ssl_session_id not in ssl_sessions:\n            ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),\n                                            random.randint(0, 0xFFFFFFFF))\n        client_sent, server_sent = ssl_sessions[ssl_session_id]\n\n        if function == \"SSL_read\":\n            seq, ack = (server_sent, client_sent)\n        else:\n            seq, ack = (client_sent, server_sent)\n\n        for writes in (\n                # PCAP record (packet) header\n                (\"=I\", int(t)),  # Timestamp seconds\n                (\"=I\", int((t * 1000000) % 1000000)),  # Timestamp microseconds\n                (\"=I\", 40 + len(data)),  # Number of octets saved\n                (\"=i\", 40 + len(data)),  # Actual length of packet\n                # IPv4 header\n                (\">B\", 0x45),  # Version and Header Length\n                (\">B\", 0),  # Type of Service\n                (\">H\", 40 + len(data)),  # Total Length\n                (\">H\", 0),  # Identification\n                (\">H\", 0x4000),  # Flags and Fragment Offset\n                (\">B\", 0xFF),  # Time to Live\n                (\">B\", 6),  # Protocol\n                (\">H\", 0),  # Header Checksum\n                (\">I\", src_addr),  # Source Address\n                (\">I\", dst_addr),  # Destination Address\n                # TCP header\n                (\">H\", src_port),  # Source Port\n                (\">H\", dst_port),  # Destination Port\n                (\">I\", seq),  # Sequence Number\n                (\">I\", ack),  # Acknowledgment Number\n                (\">H\", 0x5018),  # Header Length and Flags\n                (\">H\", 0xFFFF),  # Window Size\n                (\">H\", 0),  # Checksum\n                (\">H\", 0)):  # Urgent Pointer\n            pcap_file.write(struct.pack(writes[0], writes[1]))\n        pcap_file.write(data)\n\n        if function == \"SSL_read\":\n            server_sent += len(data)\n        else:\n            client_sent += len(data)\n        ssl_sessions[ssl_session_id] = (client_sent, server_sent)\n\n    def on_message(message, data):\n        \"\"\"Callback for errors and messages sent from Frida-injected JavaScript.\n        Logs captured packet data received from JavaScript to the console and/or a\n        pcap file. See https://www.frida.re/docs/messages/ for more detail on\n        Frida's messages.\n        Args:\n          message: A dictionary containing the message \"type\" and other fields\n              dependent on message type.\n          data: The string of captured decrypted data.\n        \"\"\"\n        if message[\"type\"] == \"error\":\n            logger.info(f\"{message}\")\n            os.kill(os.getpid(), signal.SIGTERM)\n            return\n        if len(data) == 1:\n            logger.info(f'{message[\"payload\"][\"function\"]}')\n            logger.info(f'{message[\"payload\"][\"stack\"]}')\n            return\n        p = message[\"payload\"]\n        if verbose:\n            src_addr = socket.inet_ntop(socket.AF_INET,\n                                        struct.pack(\">I\", p[\"src_addr\"]))\n            dst_addr = socket.inet_ntop(socket.AF_INET,\n                                        struct.pack(\">I\", p[\"dst_addr\"]))\n            session_id = p['ssl_session_id']\n            logger.info(f\"SSL Session: {session_id}\")\n            logger.info(\"[%s] %s:%d --> %s:%d\" % (\n                p[\"function\"],\n                src_addr,\n                p[\"src_port\"],\n                dst_addr,\n                p[\"dst_port\"]))\n            gen = hexdump.hexdump(data, result=\"generator\",only_str=True)\n            str_gen = ''.join(gen)\n            logger.info(f\"{str_gen}\")\n            logger.info(f\"{p['stack']}\")\n        if pcap:\n            log_pcap(pcap_file, p[\"ssl_session_id\"], p[\"function\"], p[\"src_addr\"],\n                     p[\"src_port\"], p[\"dst_addr\"], p[\"dst_port\"], data)\n\n    if isUsb:\n        try:\n            device = frida.get_usb_device()\n        except:\n            device = frida.get_remote_device()\n    else:\n        if host:\n            manager = frida.get_device_manager()\n            device = manager.add_remote_device(host)\n        else:\n            device = frida.get_local_device()\n\n    if isSpawn:\n        pid = device.spawn([process])\n        time.sleep(1)\n        session = device.attach(pid)\n        time.sleep(1)\n        device.resume(pid)\n    else:\n        print(\"attach\")\n        session = device.attach(process)\n    if wait > 0:\n        print(f\"wait for {wait} seconds\")\n        time.sleep(wait)\n\n    # session = frida.attach(process)\n\n    # pid = device.spawn([process])\n    # pid = process\n    # session = device.attach(pid)\n    # device.resume(pid)\n    if pcap:\n        pcap_file = open(pcap, \"wb\", 0)\n        for writes in (\n                (\"=I\", 0xa1b2c3d4),  # Magic number\n                (\"=H\", 2),  # Major version number\n                (\"=H\", 4),  # Minor version number\n                (\"=i\", time.timezone),  # GMT to local correction\n                (\"=I\", 0),  # Accuracy of timestamps\n                (\"=I\", 65535),  # Max length of captured packets\n                (\"=I\", 228)):  # Data link type (LINKTYPE_IPV4)\n            pcap_file.write(struct.pack(writes[0], writes[1]))\n\n    with open(Path(__file__).resolve().parent.joinpath(\"./script.js\"), encoding=\"utf-8\") as f:\n        _FRIDA_SCRIPT = f.read()\n        # _FRIDA_SCRIPT = session.create_script(content)\n        # print(_FRIDA_SCRIPT)\n    script = session.create_script(_FRIDA_SCRIPT)\n    script.on(\"message\", on_message)\n    script.load()\n\n    if ssllib != \"\":\n        script.exports.setssllib(ssllib)\n\n    print(\"Press Ctrl+C to stop logging.\")\n\n    def stoplog(signum, frame):\n        print('You have stoped logging.')\n        session.detach()\n        if pcap:\n            pcap_file.flush()\n            pcap_file.close()\n        exit()\n\n    signal.signal(signal.SIGINT, stoplog)\n    signal.signal(signal.SIGTERM, stoplog)\n    sys.stdin.read()\n\n\nif __name__ == \"__main__\":\n    show_banner()\n\n\n    class ArgParser(argparse.ArgumentParser):\n\n        def error(self, message):\n            print(\"ssl_logger v\" + __version__)\n            print(\"by \" + __author__)\n            print(\"Modified by BigFaceCat\")\n            print(\"Error: \" + message)\n            print()\n            print(self.format_help().replace(\"usage:\", \"Usage:\"))\n            self.exit(0)\n\n\n    parser = ArgParser(\n        add_help=False,\n        description=\"Decrypts and logs a process's SSL traffic.\",\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n        epilog=r\"\"\"\nExamples:\n    %(prog)s -pcap ssl.pcap openssl\n    %(prog)s -verbose 31337\n    %(prog)s -pcap log.pcap -verbose wget\n    %(prog)s -pcap log.pcap -ssl \"*libssl.so*\" com.bigfacecat.testdemo\n\"\"\")\n\n    args = parser.add_argument_group(\"Arguments\")\n    args.add_argument(\"-pcap\", '-p', metavar=\"<path>\", required=False,\n                      help=\"Name of PCAP file to write\")\n    args.add_argument(\"-host\", '-H', metavar=\"<192.168.1.1:27042>\", required=False,\n                      help=\"connect to remote frida-server on HOST\")\n    args.add_argument(\"-verbose\", \"-v\", required=False, action=\"store_const\", default=True,\n                      const=True, help=\"Show verbose output\")\n    args.add_argument(\"process\", metavar=\"<process name | process id>\",\n                      help=\"Process whose SSL calls to log\")\n    args.add_argument(\"-ssl\", default=\"\", metavar=\"<lib>\",\n                      help=\"SSL library to hook\")\n    args.add_argument(\"--isUsb\", \"-U\", default=False, action=\"store_true\",\n                      help=\"connect to USB device\")\n    args.add_argument(\"--isSpawn\", \"-f\", default=False, action=\"store_true\",\n                      help=\"if spawned app\")\n    args.add_argument(\"-wait\", \"-w\", type=int, metavar=\"<seconds>\", default=0,\n                      help=\"Time to wait for the process\")\n\n    parsed = parser.parse_args()\n    logger.add(f\"{parsed.process.replace('.','_')}-{int(time.time())}.log\", rotation=\"500MB\", encoding=\"utf-8\", enqueue=True, retention=\"10 days\")\n\n    ssl_log(\n        int(parsed.process) if parsed.process.isdigit() else parsed.process,\n        parsed.pcap,\n        parsed.host,\n        parsed.verbose,\n        isUsb=parsed.isUsb,\n        isSpawn=parsed.isSpawn,\n        ssllib=parsed.ssl,\n        wait=parsed.wait\n    )\n"
  },
  {
    "path": "script.js",
    "content": "/**\n   * Initializes 'addresses' dictionary and NativeFunctions.\n   */\n\"use strict\";\nrpc.exports = {\n  setssllib: function (name) {\n    console.log(\"setSSLLib => \" + name);\n    libname = name;\n    initializeGlobals();\n    return;\n  }\n};\n\nvar addresses = {};\nvar SSL_get_fd = null;\nvar SSL_get_session = null;\nvar SSL_SESSION_get_id = null;\nvar getpeername = null;\nvar getsockname = null;\nvar ntohs = null;\nvar ntohl = null;\nvar SSLstackwrite = null;\nvar SSLstackread = null;\n\nvar libname = \"*libssl*\";\n\nfunction uuid(len, radix) {\n  var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');\n  var uuid = [], i;\n  radix = radix || chars.length;\n\n  if (len) {\n    // Compact form\n    for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];\n  } else {\n    // rfc4122, version 4 form\n    var r;\n\n    // rfc4122 requires these characters\n    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';\n    uuid[14] = '4';\n\n    // Fill in random data. At i==19 set the high bits of clock sequence as\n    // per rfc4122, sec. 4.1.5\n    for (i = 0; i < 36; i++) {\n      if (!uuid[i]) {\n        r = 0 | Math.random() * 16;\n        uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];\n      }\n    }\n  }\n\n  return uuid.join('');\n}\nfunction return_zero(args) {\n  return 0;\n}\nfunction initializeGlobals() {\n  var resolver = new ApiResolver(\"module\");\n  var exps = [\n    [Process.platform == \"darwin\" ? \"*libboringssl*\" : \"*libssl*\", [\"SSL_read\", \"SSL_write\", \"SSL_get_fd\", \"SSL_get_session\", \"SSL_SESSION_get_id\"]], // for ios and Android\n    [Process.platform == \"darwin\" ? \"*libsystem*\" : \"*libc*\", [\"getpeername\", \"getsockname\", \"ntohs\", \"ntohl\"]]\n  ];\n  // console.log(exps)\n  for (var i = 0; i < exps.length; i++) {\n    var lib = exps[i][0];\n    var names = exps[i][1];\n    for (var j = 0; j < names.length; j++) {\n      var name = names[j];\n      // console.log(\"exports:\" + lib + \"!\" + name)\n      var matches = resolver.enumerateMatches(\"exports:\" + lib + \"!\" + name);\n      if (matches.length == 0) {\n        if (name == \"SSL_get_fd\") {\n          addresses[\"SSL_get_fd\"] = 0;\n          continue;\n        }\n        throw \"Could not find \" + lib + \"!\" + name;\n      }\n      else if (matches.length > 1) {\n        // Multiple matches found - prefer com.android.conscrypt library\n        var selectedMatch = null;\n        for (var k = 0; k < matches.length; k++) {\n          if (matches[k].name.indexOf(\"com.android.conscrypt\") !== -1) {\n            selectedMatch = matches[k];\n            break;\n          }\n        }\n        // If no conscrypt match, use the first one\n        if (selectedMatch === null) {\n          selectedMatch = matches[0];\n        }\n        addresses[name] = selectedMatch.address;\n        console.log(\"[*] Selected \" + name + \" from: \" + selectedMatch.name);\n      }\n      else {\n        addresses[name] = matches[0].address;\n      }\n    }\n  }\n  if (addresses[\"SSL_get_fd\"] == 0) {\n    SSL_get_fd = return_zero;\n  } else {\n    SSL_get_fd = new NativeFunction(addresses[\"SSL_get_fd\"], \"int\", [\"pointer\"]);\n  }\n  SSL_get_session = new NativeFunction(addresses[\"SSL_get_session\"], \"pointer\", [\"pointer\"]);\n  SSL_SESSION_get_id = new NativeFunction(addresses[\"SSL_SESSION_get_id\"], \"pointer\", [\"pointer\", \"pointer\"]);\n  getpeername = new NativeFunction(addresses[\"getpeername\"], \"int\", [\"int\", \"pointer\", \"pointer\"]);\n  getsockname = new NativeFunction(addresses[\"getsockname\"], \"int\", [\"int\", \"pointer\", \"pointer\"]);\n  ntohs = new NativeFunction(addresses[\"ntohs\"], \"uint16\", [\"uint16\"]);\n  ntohl = new NativeFunction(addresses[\"ntohl\"], \"uint32\", [\"uint32\"]);\n}\ninitializeGlobals();\n\nfunction ipToNumber(ip) {\n  var num = 0;\n  if (ip == \"\") {\n    return num;\n  }\n  var aNum = ip.split(\".\");\n  if (aNum.length != 4) {\n    return num;\n  }\n  num += parseInt(aNum[0]) << 0;\n  num += parseInt(aNum[1]) << 8;\n  num += parseInt(aNum[2]) << 16;\n  num += parseInt(aNum[3]) << 24;\n  num = num >>> 0;//这个很关键，不然可能会出现负数的情况\n  return num;\n}\n\n/**\n * Returns a dictionary of a sockfd's \"src_addr\", \"src_port\", \"dst_addr\", and\n * \"dst_port\".\n * @param {int} sockfd The file descriptor of the socket to inspect.\n * @param {boolean} isRead If true, the context is an SSL_read call. If\n *     false, the context is an SSL_write call.\n * @return {dict} Dictionary of sockfd's \"src_addr\", \"src_port\", \"dst_addr\",\n *     and \"dst_port\".\n */\nfunction getPortsAndAddresses(sockfd, isRead) {\n  var message = {};\n  var src_dst = [\"src\", \"dst\"];\n  for (var i = 0; i < src_dst.length; i++) {\n    if ((src_dst[i] == \"src\") ^ isRead) {\n      var sockAddr = Socket.localAddress(sockfd)\n    }\n    else {\n      var sockAddr = Socket.peerAddress(sockfd)\n    }\n    if (sockAddr == null) {\n      // 网络超时or其他原因可能导致socket被关闭\n      message[src_dst[i] + \"_port\"] = 0\n      message[src_dst[i] + \"_addr\"] = 0\n    } else {\n      message[src_dst[i] + \"_port\"] = (sockAddr.port & 0xFFFF)\n      message[src_dst[i] + \"_addr\"] = ntohl(ipToNumber(sockAddr.ip.split(\":\").pop()))\n    }\n  }\n  return message;\n}\n/**\n * Get the session_id of SSL object and return it as a hex string.\n * @param {!NativePointer} ssl A pointer to an SSL object.\n * @return {dict} A string representing the session_id of the SSL object's\n *     SSL_SESSION. For example,\n *     \"59FD71B7B90202F359D89E66AE4E61247954E28431F6C6AC46625D472FF76336\".\n */\nfunction getSslSessionId(ssl) {\n  var session = SSL_get_session(ssl);\n  if (session == 0) {\n    return 0;\n  }\n  var len = Memory.alloc(4);\n  var p = SSL_SESSION_get_id(session, len);\n  len = len.readU32();\n  var session_id = \"\";\n  for (var i = 0; i < len; i++) {\n    // Read a byte, convert it to a hex string (0xAB ==> \"AB\"), and append\n    // it to session_id.\n    session_id +=\n      (\"0\" + p.add(i).readU8().toString(16).toUpperCase()).slice(-2);\n  }\n  return session_id;\n}\n\nInterceptor.attach(addresses[\"SSL_read\"],\n  {\n    onEnter: function (args) {\n      var message = getPortsAndAddresses(SSL_get_fd(args[0]), true);\n      message[\"ssl_session_id\"] = getSslSessionId(args[0]);\n      message[\"function\"] = \"SSL_read\";\n      message[\"stack\"] = SSLstackread;\n      this.message = message;\n      this.buf = args[1];\n    },\n    onLeave: function (retval) {\n      retval |= 0; // Cast retval to 32-bit integer.\n      if (retval <= 0) {\n        return;\n      }\n      send(this.message, this.buf.readByteArray(retval));\n    }\n  });\n\nInterceptor.attach(addresses[\"SSL_write\"],\n  {\n    onEnter: function (args) {\n      var message = getPortsAndAddresses(SSL_get_fd(args[0]), false);\n      message[\"ssl_session_id\"] = getSslSessionId(args[0]);\n      message[\"function\"] = \"SSL_write\";\n      message[\"stack\"] = SSLstackwrite;\n      send(message, args[1].readByteArray(parseInt(args[2])));\n    },\n    onLeave: function (retval) {\n    }\n  });\n\nif (typeof Java !== 'undefined' && Java.available) {\n  Java.perform(function () {\n    function storeP12(pri, p7, p12Path, p12Password) {\n      var X509Certificate = Java.use(\"java.security.cert.X509Certificate\")\n      var p7X509 = Java.cast(p7, X509Certificate);\n      var chain = Java.array(\"java.security.cert.X509Certificate\", [p7X509])\n      var ks = Java.use(\"java.security.KeyStore\").getInstance(\"PKCS12\", \"BC\");\n      ks.load(null, null);\n      ks.setKeyEntry(\"client\", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);\n      try {\n        var out = Java.use(\"java.io.FileOutputStream\").$new(p12Path);\n        ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())\n      } catch (exp) {\n        console.log(exp)\n      }\n    }\n    //在服务器校验客户端的情形下，帮助dump客户端证书，并保存为p12的格式，证书密码为r0ysue\n    Java.use(\"java.security.KeyStore$PrivateKeyEntry\").getPrivateKey.implementation = function () {\n      var result = this.getPrivateKey()\n      var packageName = Java.use(\"android.app.ActivityThread\").currentApplication().getApplicationContext().getPackageName();\n      storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');\n      var message = {};\n      message[\"function\"] = \"dumpClinetCertificate=>\" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + '   pwd: r0ysue';\n      message[\"stack\"] = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new());\n      var data = Memory.alloc(1);\n      send(message, Memory.readByteArray(data, 1))\n      return result;\n    }\n    Java.use(\"java.security.KeyStore$PrivateKeyEntry\").getCertificateChain.implementation = function () {\n      var result = this.getCertificateChain()\n      var packageName = Java.use(\"android.app.ActivityThread\").currentApplication().getApplicationContext().getPackageName();\n      storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');\n      var message = {};\n      message[\"function\"] = \"dumpClinetCertificate=>\" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + '   pwd: r0ysue';\n      message[\"stack\"] = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new());\n      var data = Memory.alloc(1);\n      send(message, Memory.readByteArray(data, 1))\n      return result;\n    }\n\n    //SSLpinning helper 帮助定位证书绑定的关键代码a\n    Java.use(\"java.io.File\").$init.overload('java.io.File', 'java.lang.String').implementation = function (file, cert) {\n      var result = this.$init(file, cert)\n      var stack = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new());\n      if (file.getPath().indexOf(\"cacert\") >= 0 && stack.indexOf(\"X509TrustManagerExtensions.checkServerTrusted\") >= 0) {\n        var message = {};\n        message[\"function\"] = \"SSLpinning position locator => \" + file.getPath() + \" \" + cert;\n        message[\"stack\"] = stack;\n        var data = Memory.alloc(1);\n        send(message, Memory.readByteArray(data, 1))\n      }\n      return result;\n    }\n\n\n    Java.use(\"java.net.SocketOutputStream\").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {\n      var result = this.socketWrite0(fd, bytearry, offset, byteCount);\n      var message = {};\n      message[\"function\"] = \"HTTP_send\";\n      message[\"ssl_session_id\"] = \"\";\n      message[\"src_addr\"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(\":\")[0]).split(\"/\").pop()));\n      message[\"src_port\"] = parseInt(this.socket.value.getLocalPort().toString());\n      message[\"dst_addr\"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(\":\")[0]).split(\"/\").pop()));\n      message[\"dst_port\"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(\":\").pop());\n      message[\"stack\"] = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new()).toString();\n      var ptr = Memory.alloc(byteCount);\n      for (var i = 0; i < byteCount; ++i)\n        ptr.add(i).writeU8(bytearry[offset + i] & 0xFF);\n      send(message, ptr.readByteArray(byteCount))\n      return result;\n    }\n    Java.use(\"java.net.SocketInputStream\").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {\n      var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);\n      var message = {};\n      message[\"function\"] = \"HTTP_recv\";\n      message[\"ssl_session_id\"] = \"\";\n      message[\"src_addr\"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(\":\")[0]).split(\"/\").pop()));\n      message[\"src_port\"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(\":\").pop());\n      message[\"dst_addr\"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(\":\")[0]).split(\"/\").pop()));\n      message[\"dst_port\"] = parseInt(this.socket.value.getLocalPort());\n      message[\"stack\"] = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new()).toString();\n      if (result > 0) {\n        var ptr = Memory.alloc(result);\n        for (var i = 0; i < result; ++i)\n          ptr.add(i).writeU8(bytearry[offset + i] & 0xFF);\n        send(message, ptr.readByteArray(result))\n      }\n      return result;\n    }\n\n    if (parseFloat(Java.androidVersion)  > 8) {\n      Java.use(\"com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream\").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {\n        var result = this.write(bytearry, int1, int2);\n        SSLstackwrite = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new()).toString();\n        return result;\n      }\n      Java.use(\"com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream\").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {\n        var result = this.read(bytearry, int1, int2);\n        SSLstackread = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new()).toString();\n        return result;\n      }\n    }\n    else {\n      Java.use(\"com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream\").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {\n        var result = this.write(bytearry, int1, int2);\n        SSLstackwrite = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new()).toString();\n        return result;\n      }\n      Java.use(\"com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream\").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {\n        var result = this.read(bytearry, int1, int2);\n        SSLstackread = Java.use(\"android.util.Log\").getStackTraceString(Java.use(\"java.lang.Throwable\").$new()).toString();\n        return result;\n      }\n\n    }\n  }\n\n  )\n}\n"
  }
]