The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── LICENSE
├── README.md
├── myhexdump.py
├── pic
    ├── Sample.PNG
    ├── bypass.png
    ├── clientcer.png
    ├── difport.png
    ├── locator.png
    ├── sslunpinningcer.png
    ├── summary1.jpg
    └── summary2.jpg
├── r0capture.py
└── script.js


/LICENSE:
--------------------------------------------------------------------------------
  1 |                                  Apache License
  2 |                            Version 2.0, January 2004
  3 |                         http://www.apache.org/licenses/
  4 | 
  5 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 |    1. Definitions.
  8 | 
  9 |       "License" shall mean the terms and conditions for use, reproduction,
 10 |       and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |       "Licensor" shall mean the copyright owner or entity authorized by
 13 |       the copyright owner that is granting the License.
 14 | 
 15 |       "Legal Entity" shall mean the union of the acting entity and all
 16 |       other entities that control, are controlled by, or are under common
 17 |       control with that entity. For the purposes of this definition,
 18 |       "control" means (i) the power, direct or indirect, to cause the
 19 |       direction or management of such entity, whether by contract or
 20 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |       outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |       "You" (or "Your") shall mean an individual or Legal Entity
 24 |       exercising permissions granted by this License.
 25 | 
 26 |       "Source" form shall mean the preferred form for making modifications,
 27 |       including but not limited to software source code, documentation
 28 |       source, and configuration files.
 29 | 
 30 |       "Object" form shall mean any form resulting from mechanical
 31 |       transformation or translation of a Source form, including but
 32 |       not limited to compiled object code, generated documentation,
 33 |       and conversions to other media types.
 34 | 
 35 |       "Work" shall mean the work of authorship, whether in Source or
 36 |       Object form, made available under the License, as indicated by a
 37 |       copyright notice that is included in or attached to the work
 38 |       (an example is provided in the Appendix below).
 39 | 
 40 |       "Derivative Works" shall mean any work, whether in Source or Object
 41 |       form, that is based on (or derived from) the Work and for which the
 42 |       editorial revisions, annotations, elaborations, or other modifications
 43 |       represent, as a whole, an original work of authorship. For the purposes
 44 |       of this License, Derivative Works shall not include works that remain
 45 |       separable from, or merely link (or bind by name) to the interfaces of,
 46 |       the Work and Derivative Works thereof.
 47 | 
 48 |       "Contribution" shall mean any work of authorship, including
 49 |       the original version of the Work and any modifications or additions
 50 |       to that Work or Derivative Works thereof, that is intentionally
 51 |       submitted to Licensor for inclusion in the Work by the copyright owner
 52 |       or by an individual or Legal Entity authorized to submit on behalf of
 53 |       the copyright owner. For the purposes of this definition, "submitted"
 54 |       means any form of electronic, verbal, or written communication sent
 55 |       to the Licensor or its representatives, including but not limited to
 56 |       communication on electronic mailing lists, source code control systems,
 57 |       and issue tracking systems that are managed by, or on behalf of, the
 58 |       Licensor for the purpose of discussing and improving the Work, but
 59 |       excluding communication that is conspicuously marked or otherwise
 60 |       designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |       on behalf of whom a Contribution has been received by Licensor and
 64 |       subsequently incorporated within the Work.
 65 | 
 66 |    2. Grant of Copyright License. Subject to the terms and conditions of
 67 |       this License, each Contributor hereby grants to You a perpetual,
 68 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |       copyright license to reproduce, prepare Derivative Works of,
 70 |       publicly display, publicly perform, sublicense, and distribute the
 71 |       Work and such Derivative Works in Source or Object form.
 72 | 
 73 |    3. Grant of Patent License. Subject to the terms and conditions of
 74 |       this License, each Contributor hereby grants to You a perpetual,
 75 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |       (except as stated in this section) patent license to make, have made,
 77 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |       where such license applies only to those patent claims licensable
 79 |       by such Contributor that are necessarily infringed by their
 80 |       Contribution(s) alone or by combination of their Contribution(s)
 81 |       with the Work to which such Contribution(s) was submitted. If You
 82 |       institute patent litigation against any entity (including a
 83 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |       or a Contribution incorporated within the Work constitutes direct
 85 |       or contributory patent infringement, then any patent licenses
 86 |       granted to You under this License for that Work shall terminate
 87 |       as of the date such litigation is filed.
 88 | 
 89 |    4. Redistribution. You may reproduce and distribute copies of the
 90 |       Work or Derivative Works thereof in any medium, with or without
 91 |       modifications, and in Source or Object form, provided that You
 92 |       meet the following conditions:
 93 | 
 94 |       (a) You must give any other recipients of the Work or
 95 |           Derivative Works a copy of this License; and
 96 | 
 97 |       (b) You must cause any modified files to carry prominent notices
 98 |           stating that You changed the files; and
 99 | 
100 |       (c) You must retain, in the Source form of any Derivative Works
101 |           that You distribute, all copyright, patent, trademark, and
102 |           attribution notices from the Source form of the Work,
103 |           excluding those notices that do not pertain to any part of
104 |           the Derivative Works; and
105 | 
106 |       (d) If the Work includes a "NOTICE" text file as part of its
107 |           distribution, then any Derivative Works that You distribute must
108 |           include a readable copy of the attribution notices contained
109 |           within such NOTICE file, excluding those notices that do not
110 |           pertain to any part of the Derivative Works, in at least one
111 |           of the following places: within a NOTICE text file distributed
112 |           as part of the Derivative Works; within the Source form or
113 |           documentation, if provided along with the Derivative Works; or,
114 |           within a display generated by the Derivative Works, if and
115 |           wherever such third-party notices normally appear. The contents
116 |           of the NOTICE file are for informational purposes only and
117 |           do not modify the License. You may add Your own attribution
118 |           notices within Derivative Works that You distribute, alongside
119 |           or as an addendum to the NOTICE text from the Work, provided
120 |           that such additional attribution notices cannot be construed
121 |           as modifying the License.
122 | 
123 |       You may add Your own copyright statement to Your modifications and
124 |       may provide additional or different license terms and conditions
125 |       for use, reproduction, or distribution of Your modifications, or
126 |       for any such Derivative Works as a whole, provided Your use,
127 |       reproduction, and distribution of the Work otherwise complies with
128 |       the conditions stated in this License.
129 | 
130 |    5. Submission of Contributions. Unless You explicitly state otherwise,
131 |       any Contribution intentionally submitted for inclusion in the Work
132 |       by You to the Licensor shall be under the terms and conditions of
133 |       this License, without any additional terms or conditions.
134 |       Notwithstanding the above, nothing herein shall supersede or modify
135 |       the terms of any separate license agreement you may have executed
136 |       with Licensor regarding such Contributions.
137 | 
138 |    6. Trademarks. This License does not grant permission to use the trade
139 |       names, trademarks, service marks, or product names of the Licensor,
140 |       except as required for reasonable and customary use in describing the
141 |       origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 |    7. Disclaimer of Warranty. Unless required by applicable law or
144 |       agreed to in writing, Licensor provides the Work (and each
145 |       Contributor provides its Contributions) on an "AS IS" BASIS,
146 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |       implied, including, without limitation, any warranties or conditions
148 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |       PARTICULAR PURPOSE. You are solely responsible for determining the
150 |       appropriateness of using or redistributing the Work and assume any
151 |       risks associated with Your exercise of permissions under this License.
152 | 
153 |    8. Limitation of Liability. In no event and under no legal theory,
154 |       whether in tort (including negligence), contract, or otherwise,
155 |       unless required by applicable law (such as deliberate and grossly
156 |       negligent acts) or agreed to in writing, shall any Contributor be
157 |       liable to You for damages, including any direct, indirect, special,
158 |       incidental, or consequential damages of any character arising as a
159 |       result of this License or out of the use or inability to use the
160 |       Work (including but not limited to damages for loss of goodwill,
161 |       work stoppage, computer failure or malfunction, or any and all
162 |       other commercial damages or losses), even if such Contributor
163 |       has been advised of the possibility of such damages.
164 | 
165 |    9. Accepting Warranty or Additional Liability. While redistributing
166 |       the Work or Derivative Works thereof, You may choose to offer,
167 |       and charge a fee for, acceptance of support, warranty, indemnity,
168 |       or other liability obligations and/or rights consistent with this
169 |       License. However, in accepting such obligations, You may act only
170 |       on Your own behalf and on Your sole responsibility, not on behalf
171 |       of any other Contributor, and only if You agree to indemnify,
172 |       defend, and hold each Contributor harmless for any liability
173 |       incurred by, or claims asserted against, such Contributor by reason
174 |       of your accepting any such warranty or additional liability.
175 | 
176 |    END OF TERMS AND CONDITIONS
177 | 
178 |    APPENDIX: How to apply the Apache License to your work.
179 | 
180 |       To apply the Apache License to your work, attach the following
181 |       boilerplate notice, with the fields enclosed by brackets "[]"
182 |       replaced with your own identifying information. (Don't include
183 |       the brackets!)  The text should be enclosed in the appropriate
184 |       comment syntax for the file format. We also recommend that a
185 |       file or class name and description of purpose be included on the
186 |       same "printed page" as the copyright notice for easier
187 |       identification within third-party archives.
188 | 
189 |    Copyright [yyyy] [name of copyright owner]
190 | 
191 |    Licensed under the Apache License, Version 2.0 (the "License");
192 |    you may not use this file except in compliance with the License.
193 |    You may obtain a copy of the License at
194 | 
195 |        http://www.apache.org/licenses/LICENSE-2.0
196 | 
197 |    Unless required by applicable law or agreed to in writing, software
198 |    distributed under the License is distributed on an "AS IS" BASIS,
199 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 |    See the License for the specific language governing permissions and
201 |    limitations under the License.
202 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # r0capture
 2 | 
 3 | 安卓应用层抓包通杀脚本
 4 | 
 5 | ## 简介
 6 | 
 7 | - 仅限安卓平台,测试安卓7、8、9、10、11、12、13、14 可用 ;
 8 | - 无视所有证书校验或绑定,不用考虑任何证书的事情;
 9 | - 通杀TCP/IP四层模型中的应用层中的全部协议;
10 | - 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
11 | - 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
12 | - 无视加固,不管是整体壳还是二代壳或VMP,不用考虑加固的事情;
13 | - 如果有抓不到的情况欢迎提issue,或者直接加vx:r0ysue,进行反馈~
14 | 
15 | ### June.18th 2023 update:测试Pixel4/安卓13/KernelSU/Frida16 功能工作正常 正常抓包 导出证书
16 | 
17 | ### January.14th 2021 update:增加几个辅助功能
18 | 
19 | - 增加App收发包函数定位功能
20 | - 增加App客户端证书导出功能
21 | - 新增host连接方式“-H”,用于Frida-server监听在非标准端口时的连接
22 | 
23 | ## 用法
24 | 
25 | - 推荐环境:[https://github.com/r0ysue/AndroidSecurityStudy/blob/master/FRIDA/A01/README.md](https://github.com/r0ysue/AndroidSecurityStudy/blob/master/FRIDA/A01/README.md)
26 | 
27 | 切记仅限安卓平台7、8、9、10、11 可用 ,禁止使用模拟器。
28 | 
29 | - Spawn 模式:
30 | 
31 | `$ python3 r0capture.py -U -f com.coolapk.market -v`
32 | 
33 | - Attach 模式,抓包内容保存成pcap文件供后续分析: 
34 | 
35 | `$ python3 r0capture.py -U 酷安 -v -p iqiyi.pcap` 
36 | 
37 | 建议使用`Attach`模式,从感兴趣的地方开始抓包,并且保存成`pcap`文件,供后续使用Wireshark进行分析。
38 | > 老版本Frida使用包名,新版本Frida使用APP名。APP名必须是点开app后,frida-ps -U显示的那个app名字。
39 | 
40 | ![](pic/Sample.PNG)
41 | 
42 | - 收发包函数定位:`Spawn`和`attach`模式均默认开启;
43 | 
44 | > 可以使用`python r0capture.py -U -f cn.soulapp.android -v  >> soul3.txt`这样的命令将输出重定向至txt文件中稍后过滤内容
45 | 
46 | ![](pic/locator.png)
47 | 
48 | - 客户端证书导出功能:默认开启;必须以Spawm模式运行;
49 | 
50 | > 运行脚本之前必须手动给App加上存储卡读写权限;
51 | 
52 | > 并不是所有App都部署了服务器验证客户端的机制,只有配置了的才会在Apk中包含客户端证书
53 | 
54 | > 导出后的证书位于/sdcard/Download/包名xxx.p12路径,导出多次,每一份均可用,密码默认为:r0ysue,推荐使用[keystore-explorer](http://keystore-explorer.org/)打开查看证书。
55 | 
56 | ![](pic/clientcer.png)
57 | 
58 | - 新增host连接方式“-H”,用于Frida-server监听在非标准端口时的连接。有些App会检测Frida标准端口,因此frida-server开在非标准端口可以绕过检测。
59 | 
60 | ![](pic/difport.png)
61 | 
62 | ## 感谢[爱吃菠菜](https://bbs.pediy.com/user-760871.htm)巨巨总结的本项目知识点
63 | 
64 | ![](pic/summary1.jpg)
65 | ![](pic/summary2.jpg)
66 | 
67 | 
68 | PS:
69 | 
70 | > 这个项目基于[frida_ssl_logger](https://github.com/BigFaceCat2017/frida_ssl_logger),之所以换个名字,只是侧重点不同。 原项目的侧重点在于抓ssl和跨平台,本项目的侧重点是抓到所有的包。
71 | 
72 | > 局限:部分开发实力过强的大厂或框架,采用的是自身的SSL框架,比如WebView、小程序或Flutter,这部分目前暂未支持。部分融合App本质上已经不属于安卓App,没有使用安卓系统的框架,无法支持。当然这部分App也是少数。暂不支持HTTP/2、或HTTP/3,该部分API在安卓系统上暂未普及或布署,为App自带,无法进行通用hook。各种模拟器架构、实现、环境较为复杂,建议珍爱生命、使用真机。暂未添加多进程支持,比如:service或:push等子进程,可以使用Frida的Child-gating来支持一下。支持多进程之后要考虑pcap文件的写入锁问题,可以用frida-tool的Reactor线程锁来支持一下。
73 | 
74 | ## 以下是原项目的简介:
75 | 
76 | [https://github.com/BigFaceCat2017/frida_ssl_logger](https://github.com/BigFaceCat2017/frida_ssl_logger)
77 | 
78 | ### frida_ssl_logger
79 | ssl_logger based on frida
80 | for from https://github.com/google/ssl_logger
81 | 
82 | ### 修改内容
83 | 1. 优化了frida的JS脚本,修复了在新版frida上的语法错误;
84 | 2. 调整JS脚本,使其适配iOS和macOS,同时也兼容了Android;
85 | 3. 增加了更多的选项,使其能在多种情况下使用;
86 | 
87 | ### 安装依赖
88 | ```
89 | Python版本>=3.6
90 | pip install loguru
91 | pip install click
92 | ```
93 | ### Usage
94 |   ```shell
95 |     python3 ./ssl_logger.py  -U -f com.bfc.mm
96 |     python3 ./ssl_logger.py -v  -p test.pcap  6666
97 |   ````
98 | 
99 | 


--------------------------------------------------------------------------------
/myhexdump.py:
--------------------------------------------------------------------------------
  1 | # -*- coding: utf-8 -*-
  2 | # !/usr/bin/env python
  3 | # -*- coding: latin-1 -*-
  4 | 
  5 | # <-- removing this magic comment breaks Python 3.4 on Windows
  6 | """
  7 | 1. Dump binary data to the following text format:
  8 | 
  9 | 00000000: 00 00 00 5B 68 65 78 64  75 6D 70 5D 00 00 00 00  ...[hexdump]....
 10 | 00000010: 00 11 22 33 44 55 66 77  88 99 AA BB CC DD EE FF  .."3DUfw........
 11 | 
 12 | It is similar to the one used by:
 13 | Scapy
 14 | 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00  ...[hexdump]....
 15 | 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF  .."3DUfw........
 16 | 
 17 | Far Manager
 18 | 000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00     [hexdump]
 19 | 000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 AA BB CC DD EE FF   ?"3DUfwˆ™ª»ÌÝîÿ
 20 | 
 21 | 
 22 | 2. Restore binary data from the formats above as well
 23 |    as from less exotic strings of raw hex
 24 | 
 25 | """
 26 | 
 27 | __version__ = '3.3'
 28 | __author__ = 'anatoly techtonik <techtonik@gmail.com>'
 29 | __license__ = 'Public Domain'
 30 | 
 31 | __history__ = \
 32 |     """
 33 |     3.3 (2015-01-22)
 34 |      * accept input from sys.stdin if "-" is specified
 35 |        for both dump and restore (issue #1)
 36 |      * new normalize_py() helper to set sys.stdout to
 37 |        binary mode on Windows
 38 |     
 39 |     3.2 (2015-07-02)
 40 |      * hexdump is now packaged as .zip on all platforms
 41 |        (on Linux created archive was tar.gz)
 42 |      * .zip is executable! try `python hexdump-3.2.zip`
 43 |      * dump() now accepts configurable separator, patch
 44 |        by Ian Land (PR #3)
 45 |     
 46 |     3.1 (2014-10-20)
 47 |      * implemented workaround against mysterious coding
 48 |        issue with Python 3 (see revision 51302cf)
 49 |      * fix Python 3 installs for systems where UTF-8 is
 50 |        not default (Windows), thanks to George Schizas
 51 |        (the problem was caused by reading of README.txt)
 52 |     
 53 |     3.0 (2014-09-07)
 54 |      * remove unused int2byte() helper
 55 |      * add dehex(text) helper to convert hex string
 56 |        to binary data
 57 |      * add 'size' argument to dump() helper to specify
 58 |        length of chunks
 59 |     
 60 |     2.0 (2014-02-02)
 61 |      * add --restore option to command line mode to get
 62 |        binary data back from hex dump
 63 |      * support saving test output with `--test logfile`
 64 |      * restore() from hex strings without spaces
 65 |      * restore() now raises TypeError if input data is
 66 |        not string
 67 |      * hexdump() and dumpgen() now don't return unicode
 68 |        strings in Python 2.x when generator is requested
 69 |     
 70 |     1.0 (2013-12-30)
 71 |      * length of address is reduced from 10 to 8
 72 |      * hexdump() got new 'result' keyword argument, it
 73 |        can be either 'print', 'generator' or 'return'
 74 |      * actual dumping logic is now in new dumpgen()
 75 |        generator function
 76 |      * new dump(binary) function that takes binary data
 77 |        and returns string like "66 6F 72 6D 61 74"
 78 |      * new genchunks(mixed, size) function that chunks
 79 |        both sequences and file like objects
 80 |     
 81 |     0.5 (2013-06-10)
 82 |      * hexdump is now also a command line utility (no
 83 |        restore yet)
 84 |     
 85 |     0.4 (2013-06-09)
 86 |      * fix installation with Python 3 for non English
 87 |        versions of Windows, thanks to George Schizas
 88 |     
 89 |     0.3 (2013-04-29)
 90 |      * fully Python 3 compatible
 91 |     
 92 |     0.2 (2013-04-28)
 93 |      * restore() to recover binary data from a hex dump in
 94 |        native, Far Manager and Scapy text formats (others
 95 |        might work as well)
 96 |      * restore() is Python 3 compatible
 97 |     
 98 |     0.1 (2013-04-28)
 99 |      * working hexdump() function for Python 2
100 |     """
101 | 
102 | import binascii  # binascii is required for Python 3
103 | import sys
104 | 
105 | # --- constants
106 | PY3K = sys.version_info >= (3, 0)
107 | 
108 | 
109 | # --- workaround against Python consistency issues
110 | def normalize_py():
111 |     ''' Problem 001 - sys.stdout in Python is by default opened in
112 |         text mode, and writes to this stdout produce corrupted binary
113 |         data on Windows
114 | 
115 |             python -c "import sys; sys.stdout.write('_\n_')" > file
116 |             python -c "print(repr(open('file', 'rb').read()))"
117 |     '''
118 |     if sys.platform == "win32":
119 |         # set sys.stdout to binary mode on Windows
120 |         import os, msvcrt
121 |         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
122 | 
123 | 
124 | # --- - chunking helpers
125 | def chunks(seq, size):
126 |     '''Generator that cuts sequence (bytes, memoryview, etc.)
127 |        into chunks of given size. If `seq` length is not multiply
128 |        of `size`, the lengh of the last chunk returned will be
129 |        less than requested.
130 | 
131 |        >>> list( chunks([1,2,3,4,5,6,7], 3) )
132 |        [[1, 2, 3], [4, 5, 6], [7]]
133 |     '''
134 |     d, m = divmod(len(seq), size)
135 |     for i in range(d):
136 |         yield seq[i * size:(i + 1) * size]
137 |     if m:
138 |         yield seq[d * size:]
139 | 
140 | 
141 | def chunkread(f, size):
142 |     '''Generator that reads from file like object. May return less
143 |        data than requested on the last read.'''
144 |     c = f.read(size)
145 |     while len(c):
146 |         yield c
147 |         c = f.read(size)
148 | 
149 | 
150 | def genchunks(mixed, size):
151 |     '''Generator to chunk binary sequences or file like objects.
152 |        The size of the last chunk returned may be less than
153 |        requested.'''
154 |     if hasattr(mixed, 'read'):
155 |         return chunkread(mixed, size)
156 |     else:
157 |         return chunks(mixed, size)
158 | 
159 | 
160 | # --- - /chunking helpers
161 | 
162 | 
163 | def dehex(hextext):
164 |     """
165 |     Convert from hex string to binary data stripping
166 |     whitespaces from `hextext` if necessary.
167 |     """
168 |     if PY3K:
169 |         return bytes.fromhex(hextext)
170 |     else:
171 |         hextext = "".join(hextext.split())
172 |         return hextext.decode('hex')
173 | 
174 | 
175 | def dump(binary, size=2, sep=' '):
176 |     '''
177 |     Convert binary data (bytes in Python 3 and str in
178 |     Python 2) to hex string like '00 DE AD BE EF'.
179 |     `size` argument specifies length of text chunks
180 |     and `sep` sets chunk separator.
181 |     '''
182 |     hexstr = binascii.hexlify(binary)
183 |     if PY3K:
184 |         hexstr = hexstr.decode('ascii')
185 |     return sep.join(chunks(hexstr.upper(), size))
186 | 
187 | 
188 | def dumpgen(data, only_str):
189 |     '''
190 |     Generator that produces strings:
191 | 
192 |     '00000000: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................'
193 |     '''
194 |     generator = genchunks(data, 16)
195 |     for addr, d in enumerate(generator):
196 |         line = ""
197 |         if not only_str:
198 |             # 00000000:
199 |             line = '%08X: ' % (addr * 16)
200 |             # 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
201 |             dumpstr = dump(d)
202 |             line += dumpstr[:8 * 3]
203 |             if len(d) > 8:  # insert separator if needed
204 |                 line += ' ' + dumpstr[8 * 3:]
205 |             # ................
206 |             # calculate indentation, which may be different for the last line
207 |             pad = 2
208 |             if len(d) < 16:
209 |                 pad += 3 * (16 - len(d))
210 |             if len(d) <= 8:
211 |                 pad += 1
212 |             line += ' ' * pad
213 | 
214 |         for byte in d:
215 |             # printable ASCII range 0x20 to 0x7E
216 |             if not PY3K:
217 |                 byte = ord(byte)
218 |             if 0x20 <= byte <= 0x7E:
219 |                 line += chr(byte)
220 |             else:
221 |                 line += '.'
222 |         yield line
223 | 
224 | 
225 | def hexdump(data, result='print', only_str=False):
226 |     '''
227 |     Transform binary data to the hex dump text format:
228 | 
229 |     00000000: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
230 | 
231 |       [x] data argument as a binary string
232 |       [x] data argument as a file like object
233 | 
234 |     Returns result depending on the `result` argument:
235 |       'print'     - prints line by line
236 |       'return'    - returns single string
237 |       'generator' - returns generator that produces lines
238 |     '''
239 |     if PY3K and type(data) == str:
240 |         raise TypeError('Abstract unicode data (expected bytes sequence)')
241 | 
242 |     gen = dumpgen(data, only_str=only_str)
243 |     if result == 'generator':
244 |         return gen
245 |     elif result == 'return':
246 |         return '\n'.join(gen)
247 |     elif result == 'print':
248 |         for line in gen:
249 |             print(line)
250 |     else:
251 |         raise ValueError('Unknown value of `result` argument')
252 | 
253 | 
254 | def restore(dump):
255 |     '''
256 |     Restore binary data from a hex dump.
257 |       [x] dump argument as a string
258 |       [ ] dump argument as a line iterator
259 | 
260 |     Supported formats:
261 |       [x] hexdump.hexdump
262 |       [x] Scapy
263 |       [x] Far Manager
264 |     '''
265 |     minhexwidth = 2 * 16  # minimal width of the hex part - 00000... style
266 |     bytehexwidth = 3 * 16 - 1  # min width for a bytewise dump - 00 00 ... style
267 | 
268 |     result = bytes() if PY3K else ''
269 |     if type(dump) != str:
270 |         raise TypeError('Invalid data for restore')
271 | 
272 |     text = dump.strip()  # ignore surrounding empty lines
273 |     for line in text.split('\n'):
274 |         # strip address part
275 |         addrend = line.find(':')
276 |         if 0 < addrend < minhexwidth:  # : is not in ascii part
277 |             line = line[addrend + 1:]
278 |         line = line.lstrip()
279 |         # check dump type
280 |         if line[2] == ' ':  # 00 00 00 ...  type of dump
281 |             # check separator
282 |             sepstart = (2 + 1) * 7 + 2  # ('00'+' ')*7+'00'
283 |             sep = line[sepstart:sepstart + 3]
284 |             if sep[:2] == '  ' and sep[2:] != ' ':  # ...00 00  00 00...
285 |                 hexdata = line[:bytehexwidth + 1]
286 |             elif sep[2:] == ' ':  # ...00 00 | 00 00...  - Far Manager
287 |                 hexdata = line[:sepstart] + line[sepstart + 3:bytehexwidth + 2]
288 |             else:  # ...00 00 00 00... - Scapy, no separator
289 |                 hexdata = line[:bytehexwidth]
290 |             line = hexdata
291 |         result += dehex(line)
292 |     return result
293 | 
294 | 
295 | def runtest(logfile=None):
296 |     '''Run hexdump tests. Requires hexfile.bin to be in the same
297 |        directory as hexdump.py itself'''
298 | 
299 |     class TeeOutput(object):
300 |         def __init__(self, stream1, stream2):
301 |             self.outputs = [stream1, stream2]
302 | 
303 |         # -- methods from sys.stdout / sys.stderr
304 |         def write(self, data):
305 |             for stream in self.outputs:
306 |                 if PY3K:
307 |                     if 'b' in stream.mode:
308 |                         data = data.encode('utf-8')
309 |                 stream.write(data)
310 |                 stream.flush()
311 | 
312 |         def tell(self):
313 |             raise IOError
314 | 
315 |         def flush(self):
316 |             for stream in self.outputs:
317 |                 stream.flush()
318 |         # --/ sys.stdout
319 | 
320 |     if logfile:
321 |         openlog = open(logfile, 'wb')
322 |         # copy stdout and stderr streams to log file
323 |         savedstd = sys.stderr, sys.stdout
324 |         sys.stderr = TeeOutput(sys.stderr, openlog)
325 |         sys.stdout = TeeOutput(sys.stdout, openlog)
326 | 
327 |     def echo(msg, linefeed=True):
328 |         sys.stdout.write(msg)
329 |         if linefeed:
330 |             sys.stdout.write('\n')
331 | 
332 |     expected = '''\
333 | 00000000: 00 00 00 5B 68 65 78 64  75 6D 70 5D 00 00 00 00  ...[hexdump]....
334 | 00000010: 00 11 22 33 44 55 66 77  88 99 0A BB CC DD EE FF  .."3DUfw........\
335 | '''
336 | 
337 |     # get path to hexfile.bin
338 |     # this doesn't work from .zip
339 |     #   import os.path as osp
340 |     #   hexfile = osp.dirname(osp.abspath(__file__)) + '/hexfile.bin'
341 |     # this doesn't work either
342 |     #   hexfile = osp.dirname(sys.modules[__name__].__file__) + '/hexfile.bin'
343 |     # this works
344 |     import pkgutil
345 |     bin = pkgutil.get_data('hexdump', 'data/hexfile.bin')
346 | 
347 |     # varios length of input data
348 |     hexdump(b'zzzz' * 12)
349 |     hexdump(b'o' * 17)
350 |     hexdump(b'p' * 24)
351 |     hexdump(b'q' * 26)
352 |     # allowable character set filter
353 |     hexdump(b'line\nfeed\r\ntest')
354 |     hexdump(b'\x00\x00\x00\x5B\x68\x65\x78\x64\x75\x6D\x70\x5D\x00\x00\x00\x00'
355 |             b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\x0A\xBB\xCC\xDD\xEE\xFF')
356 |     print('---')
357 |     # dumping file-like binary object to screen (default behavior)
358 |     hexdump(bin)
359 |     print('return output')
360 |     hexout = hexdump(bin, result='return')
361 |     assert hexout == expected, 'returned hex didn\'t match'
362 |     print('return generator')
363 |     hexgen = hexdump(bin, result='generator')
364 |     assert next(hexgen) == expected.split('\n')[0], 'hex generator 1 didn\'t match'
365 |     assert next(hexgen) == expected.split('\n')[1], 'hex generator 2 didn\'t match'
366 | 
367 |     # binary restore test
368 |     bindata = restore(
369 |         '''
370 |         00000000: 00 00 00 5B 68 65 78 64  75 6D 70 5D 00 00 00 00  ...[hexdump]....
371 |         00000010: 00 11 22 33 44 55 66 77  88 99 0A BB CC DD EE FF  .."3DUfw........
372 |         ''')
373 |     echo('restore check ', linefeed=False)
374 |     assert bin == bindata, 'restore check failed'
375 |     echo('passed')
376 | 
377 |     far = \
378 |         '''
379 |         000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00     [hexdump]
380 |         000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 0A BB CC DD EE FF   ?"3DUfwˆ™ª»ÌÝîÿ
381 |         '''
382 |     echo('restore far format ', linefeed=False)
383 |     assert bin == restore(far), 'far format check failed'
384 |     echo('passed')
385 | 
386 |     scapy = '''\
387 | 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00  ...[hexdump]....
388 | 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF  .."3DUfw........
389 | '''
390 |     echo('restore scapy format ', linefeed=False)
391 |     assert bin == restore(scapy), 'scapy format check failed'
392 |     echo('passed')
393 | 
394 |     if not PY3K:
395 |         assert restore('5B68657864756D705D') == '[hexdump]', 'no space check failed'
396 |         assert dump('\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e'
397 |     else:
398 |         assert restore('5B68657864756D705D') == b'[hexdump]', 'no space check failed'
399 |         assert dump(b'\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e'
400 | 
401 |     print('---[test file hexdumping]---')
402 | 
403 |     import os
404 |     import tempfile
405 |     hexfile = tempfile.NamedTemporaryFile(delete=False)
406 |     try:
407 |         hexfile.write(bin)
408 |         hexfile.close()
409 |         hexdump(open(hexfile.name, 'rb'))
410 |     finally:
411 |         os.remove(hexfile.name)
412 |     if logfile:
413 |         sys.stderr, sys.stdout = savedstd
414 |         openlog.close()
415 | 
416 | 
417 | def main():
418 |     from optparse import OptionParser
419 |     parser = OptionParser(usage='''
420 |   %prog [binfile|-]
421 |   %prog -r hexfile
422 |   %prog --test [logfile]''', version=__version__)
423 |     parser.add_option('-r', '--restore', action='store_true',
424 |                       help='restore binary from hex dump')
425 |     parser.add_option('--test', action='store_true', help='run hexdump sanity checks')
426 | 
427 |     options, args = parser.parse_args()
428 | 
429 |     if options.test:
430 |         if args:
431 |             runtest(logfile=args[0])
432 |         else:
433 |             runtest()
434 |     elif not args or len(args) > 1:
435 |         parser.print_help()
436 |         sys.exit(-1)
437 |     else:
438 |         ## dump file
439 |         if not options.restore:
440 |             # [x] memory effective dump
441 |             if args[0] == '-':
442 |                 if not PY3K:
443 |                     hexdump(sys.stdin)
444 |                 else:
445 |                     hexdump(sys.stdin.buffer)
446 |             else:
447 |                 hexdump(open(args[0], 'rb'))
448 | 
449 |         ## restore file
450 |         else:
451 |             # prepare input stream
452 |             if args[0] == '-':
453 |                 instream = sys.stdin
454 |             else:
455 |                 if PY3K:
456 |                     instream = open(args[0])
457 |                 else:
458 |                     instream = open(args[0], 'rb')
459 | 
460 |             # output stream
461 |             # [ ] memory efficient restore
462 |             if PY3K:
463 |                 sys.stdout.buffer.write(restore(instream.read()))
464 |             else:
465 |                 # Windows - binary mode for sys.stdout to prevent data corruption
466 |                 normalize_py()
467 |                 sys.stdout.write(restore(instream.read()))
468 | 
469 | 
470 | if __name__ == '__main__':
471 |     main()
472 | 
473 | # [x] file restore from command line utility
474 | # [ ] write dump with LF on Windows for consistency
475 | # [ ] encoding param for hexdump()ing Python 3 str if anybody requests that
476 | 
477 | # [ ] document chunking API
478 | # [ ] document hexdump API
479 | # [ ] blog about sys.stdout text mode problem on Windows
480 | 


--------------------------------------------------------------------------------
/pic/Sample.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/Sample.PNG


--------------------------------------------------------------------------------
/pic/bypass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/bypass.png


--------------------------------------------------------------------------------
/pic/clientcer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/clientcer.png


--------------------------------------------------------------------------------
/pic/difport.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/difport.png


--------------------------------------------------------------------------------
/pic/locator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/locator.png


--------------------------------------------------------------------------------
/pic/sslunpinningcer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/sslunpinningcer.png


--------------------------------------------------------------------------------
/pic/summary1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/summary1.jpg


--------------------------------------------------------------------------------
/pic/summary2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/r0ysue/r0capture/f8f06133c505a08fca6dff147c1e4d93953ca43c/pic/summary2.jpg


--------------------------------------------------------------------------------
/r0capture.py:
--------------------------------------------------------------------------------
  1 | # Copyright 2017 Google Inc. All Rights Reserved.
  2 | 
  3 | # Licensed under the Apache License, Version 2.0 (the "License");
  4 | # you may not use this file except in compliance with the License.
  5 | # You may obtain a copy of the License at
  6 | #
  7 | #     http://www.apache.org/licenses/LICENSE-2.0
  8 | #
  9 | # Unless required by applicable law or agreed to in writing, software
 10 | # distributed under the License is distributed on an "AS IS" BASIS,
 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | # See the License for the specific language governing permissions and
 13 | # limitations under the License.
 14 | 
 15 | """Decrypts and logs a process's SSL traffic.
 16 | Hooks the functions SSL_read() and SSL_write() in a given process and logs the
 17 | decrypted data to the console and/or to a pcap file.
 18 |   Typical usage example:
 19 |   ssl_log("wget", "log.pcap", True)
 20 | Dependencies:
 21 |   frida (https://www.frida.re/):
 22 |     sudo pip install frida
 23 |   hexdump (https://bitbucket.org/techtonik/hexdump/) if using verbose output:
 24 |     sudo pip install hexdump
 25 | """
 26 | 
 27 | __author__ = "geffner@google.com (Jason Geffner)"
 28 | __version__ = "2.0"
 29 | 
 30 | """
 31 | # r0capture
 32 | 
 33 | ID: r0ysue 
 34 | 
 35 | 安卓应用层抓包通杀脚本
 36 | 
 37 | https://github.com/r0ysue/r0capture
 38 | 
 39 | ## 简介
 40 | 
 41 | - 仅限安卓平台,测试安卓7、8、9、10 可用 ;
 42 | - 无视所有证书校验或绑定,无视任何证书;
 43 | - 通杀TCP/IP四层模型中的应用层中的全部协议;
 44 | - 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
 45 | - 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
 46 | """
 47 | 
 48 | # Windows版本需要安装库:
 49 | # pip install 'win_inet_pton'
 50 | # pip install hexdump
 51 | import argparse
 52 | import os
 53 | import pprint
 54 | import random
 55 | import signal
 56 | import socket
 57 | import struct
 58 | import sys
 59 | import time
 60 | from pathlib import Path
 61 | 
 62 | import frida
 63 | from loguru import logger
 64 | 
 65 | try:
 66 |     if os.name == 'nt':
 67 |         import win_inet_pton
 68 | except ImportError:
 69 |     # win_inet_pton import error
 70 |     pass
 71 | 
 72 | try:
 73 |     import myhexdump as hexdump # pylint: disable=g-import-not-at-top
 74 | except ImportError:
 75 |     pass
 76 | try:
 77 |     from shutil import get_terminal_size as get_terminal_size
 78 | except:
 79 |     try:
 80 |         from backports.shutil_get_terminal_size import get_terminal_size as get_terminal_size
 81 |     except:
 82 |         pass
 83 | 
 84 | try:
 85 |     import click
 86 | except:
 87 |     class click:
 88 |         @staticmethod
 89 |         def secho(message=None, **kwargs):
 90 |             print(message)
 91 | 
 92 |         @staticmethod
 93 |         def style(**kwargs):
 94 |             raise Exception("unsupported style")
 95 | banner = """
 96 | --------------------------------------------------------------------------------------------
 97 |            .oooo.                                      .                                  
 98 |           d8P'`Y8b                                   .o8                                  
 99 | oooo d8b 888    888  .ooooo.   .oooo.   oo.ooooo.  .o888oo oooo  oooo  oooo d8b  .ooooo.  
100 | `888""8P 888    888 d88' `"Y8 `P  )88b   888' `88b   888   `888  `888  `888""8P d88' `88b 
101 |  888     888    888 888        .oP"888   888   888   888    888   888   888     888ooo888 
102 |  888     `88b  d88' 888   .o8 d8(  888   888   888   888 .  888   888   888     888    .o 
103 | d888b     `Y8bd8P'  `Y8bod8P' `Y888""8o  888bod8P'   "888"  `V88V"V8P' d888b    `Y8bod8P' 
104 |                                          888                                              
105 |                                         o888o                                                                                                                                       
106 |                     https://github.com/r0ysue/r0capture
107 | --------------------------------------------------------------------------------------------\n
108 | """
109 | 
110 | 
111 | def show_banner():
112 |     colors = ['bright_red', 'bright_green', 'bright_blue', 'cyan', 'magenta']
113 |     try:
114 |         click.style('color test', fg='bright_red')
115 |     except:
116 |         colors = ['red', 'green', 'blue', 'cyan', 'magenta']
117 |     try:
118 |         columns = get_terminal_size().columns
119 |         if columns >= len(banner.splitlines()[1]):
120 |             for line in banner.splitlines():
121 |                 click.secho(line, fg=random.choice(colors))
122 |     except:
123 |         pass
124 | 
125 | 
126 | # ssl_session[<SSL_SESSION id>] = (<bytes sent by client>,
127 | #                                  <bytes sent by server>)
128 | ssl_sessions = {}
129 | 
130 | 
131 | def ssl_log(process, pcap=None, host=False, verbose=False, isUsb=False, ssllib="", isSpawn=True, wait=0):
132 |     """Decrypts and logs a process's SSL traffic.
133 |     Hooks the functions SSL_read() and SSL_write() in a given process and logs
134 |     the decrypted data to the console and/or to a pcap file.
135 |     Args:
136 |       process: The target process's name (as a string) or process ID (as an int).
137 |       pcap: The file path to which the pcap file should be written.
138 |       verbose: If True, log the decrypted traffic to the console.
139 |     Raises:
140 |       NotImplementedError: Not running on a Linux or macOS system.
141 |     """
142 | 
143 |     # if platform.system() not in ("Darwin", "Linux"):
144 |     #   raise NotImplementedError("This function is only implemented for Linux and "
145 |     #                             "macOS systems.")
146 | 
147 |     def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
148 |                  dst_addr, dst_port, data):
149 |         """Writes the captured data to a pcap file.
150 |         Args:
151 |           pcap_file: The opened pcap file.
152 |           ssl_session_id: The SSL session ID for the communication.
153 |           function: The function that was intercepted ("SSL_read" or "SSL_write").
154 |           src_addr: The source address of the logged packet.
155 |           src_port: The source port of the logged packet.
156 |           dst_addr: The destination address of the logged packet.
157 |           dst_port: The destination port of the logged packet.
158 |           data: The decrypted packet data.
159 |         """
160 |         t = time.time()
161 | 
162 |         if ssl_session_id not in ssl_sessions:
163 |             ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),
164 |                                             random.randint(0, 0xFFFFFFFF))
165 |         client_sent, server_sent = ssl_sessions[ssl_session_id]
166 | 
167 |         if function == "SSL_read":
168 |             seq, ack = (server_sent, client_sent)
169 |         else:
170 |             seq, ack = (client_sent, server_sent)
171 | 
172 |         for writes in (
173 |                 # PCAP record (packet) header
174 |                 ("=I", int(t)),  # Timestamp seconds
175 |                 ("=I", int((t * 1000000) % 1000000)),  # Timestamp microseconds
176 |                 ("=I", 40 + len(data)),  # Number of octets saved
177 |                 ("=i", 40 + len(data)),  # Actual length of packet
178 |                 # IPv4 header
179 |                 (">B", 0x45),  # Version and Header Length
180 |                 (">B", 0),  # Type of Service
181 |                 (">H", 40 + len(data)),  # Total Length
182 |                 (">H", 0),  # Identification
183 |                 (">H", 0x4000),  # Flags and Fragment Offset
184 |                 (">B", 0xFF),  # Time to Live
185 |                 (">B", 6),  # Protocol
186 |                 (">H", 0),  # Header Checksum
187 |                 (">I", src_addr),  # Source Address
188 |                 (">I", dst_addr),  # Destination Address
189 |                 # TCP header
190 |                 (">H", src_port),  # Source Port
191 |                 (">H", dst_port),  # Destination Port
192 |                 (">I", seq),  # Sequence Number
193 |                 (">I", ack),  # Acknowledgment Number
194 |                 (">H", 0x5018),  # Header Length and Flags
195 |                 (">H", 0xFFFF),  # Window Size
196 |                 (">H", 0),  # Checksum
197 |                 (">H", 0)):  # Urgent Pointer
198 |             pcap_file.write(struct.pack(writes[0], writes[1]))
199 |         pcap_file.write(data)
200 | 
201 |         if function == "SSL_read":
202 |             server_sent += len(data)
203 |         else:
204 |             client_sent += len(data)
205 |         ssl_sessions[ssl_session_id] = (client_sent, server_sent)
206 | 
207 |     def on_message(message, data):
208 |         """Callback for errors and messages sent from Frida-injected JavaScript.
209 |         Logs captured packet data received from JavaScript to the console and/or a
210 |         pcap file. See https://www.frida.re/docs/messages/ for more detail on
211 |         Frida's messages.
212 |         Args:
213 |           message: A dictionary containing the message "type" and other fields
214 |               dependent on message type.
215 |           data: The string of captured decrypted data.
216 |         """
217 |         if message["type"] == "error":
218 |             logger.info(f"{message}")
219 |             os.kill(os.getpid(), signal.SIGTERM)
220 |             return
221 |         if len(data) == 1:
222 |             logger.info(f'{message["payload"]["function"]}')
223 |             logger.info(f'{message["payload"]["stack"]}')
224 |             return
225 |         p = message["payload"]
226 |         if verbose:
227 |             src_addr = socket.inet_ntop(socket.AF_INET,
228 |                                         struct.pack(">I", p["src_addr"]))
229 |             dst_addr = socket.inet_ntop(socket.AF_INET,
230 |                                         struct.pack(">I", p["dst_addr"]))
231 |             session_id = p['ssl_session_id']
232 |             logger.info(f"SSL Session: {session_id}")
233 |             logger.info("[%s] %s:%d --> %s:%d" % (
234 |                 p["function"],
235 |                 src_addr,
236 |                 p["src_port"],
237 |                 dst_addr,
238 |                 p["dst_port"]))
239 |             gen = hexdump.hexdump(data, result="generator",only_str=True)
240 |             str_gen = ''.join(gen)
241 |             logger.info(f"{str_gen}")
242 |             logger.info(f"{p['stack']}")
243 |         if pcap:
244 |             log_pcap(pcap_file, p["ssl_session_id"], p["function"], p["src_addr"],
245 |                      p["src_port"], p["dst_addr"], p["dst_port"], data)
246 | 
247 |     if isUsb:
248 |         try:
249 |             device = frida.get_usb_device()
250 |         except:
251 |             device = frida.get_remote_device()
252 |     else:
253 |         if host:
254 |             manager = frida.get_device_manager()
255 |             device = manager.add_remote_device(host)
256 |         else:
257 |             device = frida.get_local_device()
258 | 
259 |     if isSpawn:
260 |         pid = device.spawn([process])
261 |         time.sleep(1)
262 |         session = device.attach(pid)
263 |         time.sleep(1)
264 |         device.resume(pid)
265 |     else:
266 |         print("attach")
267 |         session = device.attach(process)
268 |     if wait > 0:
269 |         print(f"wait for {wait} seconds")
270 |         time.sleep(wait)
271 | 
272 |     # session = frida.attach(process)
273 | 
274 |     # pid = device.spawn([process])
275 |     # pid = process
276 |     # session = device.attach(pid)
277 |     # device.resume(pid)
278 |     if pcap:
279 |         pcap_file = open(pcap, "wb", 0)
280 |         for writes in (
281 |                 ("=I", 0xa1b2c3d4),  # Magic number
282 |                 ("=H", 2),  # Major version number
283 |                 ("=H", 4),  # Minor version number
284 |                 ("=i", time.timezone),  # GMT to local correction
285 |                 ("=I", 0),  # Accuracy of timestamps
286 |                 ("=I", 65535),  # Max length of captured packets
287 |                 ("=I", 228)):  # Data link type (LINKTYPE_IPV4)
288 |             pcap_file.write(struct.pack(writes[0], writes[1]))
289 | 
290 |     with open(Path(__file__).resolve().parent.joinpath("./script.js"), encoding="utf-8") as f:
291 |         _FRIDA_SCRIPT = f.read()
292 |         # _FRIDA_SCRIPT = session.create_script(content)
293 |         # print(_FRIDA_SCRIPT)
294 |     script = session.create_script(_FRIDA_SCRIPT)
295 |     script.on("message", on_message)
296 |     script.load()
297 | 
298 |     if ssllib != "":
299 |         script.exports.setssllib(ssllib)
300 | 
301 |     print("Press Ctrl+C to stop logging.")
302 | 
303 |     def stoplog(signum, frame):
304 |         print('You have stoped logging.')
305 |         session.detach()
306 |         if pcap:
307 |             pcap_file.flush()
308 |             pcap_file.close()
309 |         exit()
310 | 
311 |     signal.signal(signal.SIGINT, stoplog)
312 |     signal.signal(signal.SIGTERM, stoplog)
313 |     sys.stdin.read()
314 | 
315 | 
316 | if __name__ == "__main__":
317 |     show_banner()
318 | 
319 | 
320 |     class ArgParser(argparse.ArgumentParser):
321 | 
322 |         def error(self, message):
323 |             print("ssl_logger v" + __version__)
324 |             print("by " + __author__)
325 |             print("Modified by BigFaceCat")
326 |             print("Error: " + message)
327 |             print()
328 |             print(self.format_help().replace("usage:", "Usage:"))
329 |             self.exit(0)
330 | 
331 | 
332 |     parser = ArgParser(
333 |         add_help=False,
334 |         description="Decrypts and logs a process's SSL traffic.",
335 |         formatter_class=argparse.RawDescriptionHelpFormatter,
336 |         epilog=r"""
337 | Examples:
338 |     %(prog)s -pcap ssl.pcap openssl
339 |     %(prog)s -verbose 31337
340 |     %(prog)s -pcap log.pcap -verbose wget
341 |     %(prog)s -pcap log.pcap -ssl "*libssl.so*" com.bigfacecat.testdemo
342 | """)
343 | 
344 |     args = parser.add_argument_group("Arguments")
345 |     args.add_argument("-pcap", '-p', metavar="<path>", required=False,
346 |                       help="Name of PCAP file to write")
347 |     args.add_argument("-host", '-H', metavar="<192.168.1.1:27042>", required=False,
348 |                       help="connect to remote frida-server on HOST")
349 |     args.add_argument("-verbose", "-v", required=False, action="store_const", default=True,
350 |                       const=True, help="Show verbose output")
351 |     args.add_argument("process", metavar="<process name | process id>",
352 |                       help="Process whose SSL calls to log")
353 |     args.add_argument("-ssl", default="", metavar="<lib>",
354 |                       help="SSL library to hook")
355 |     args.add_argument("--isUsb", "-U", default=False, action="store_true",
356 |                       help="connect to USB device")
357 |     args.add_argument("--isSpawn", "-f", default=False, action="store_true",
358 |                       help="if spawned app")
359 |     args.add_argument("-wait", "-w", type=int, metavar="<seconds>", default=0,
360 |                       help="Time to wait for the process")
361 | 
362 |     parsed = parser.parse_args()
363 |     logger.add(f"{parsed.process.replace('.','_')}-{int(time.time())}.log", rotation="500MB", encoding="utf-8", enqueue=True, retention="10 days")
364 | 
365 |     ssl_log(
366 |         int(parsed.process) if parsed.process.isdigit() else parsed.process,
367 |         parsed.pcap,
368 |         parsed.host,
369 |         parsed.verbose,
370 |         isUsb=parsed.isUsb,
371 |         isSpawn=parsed.isSpawn,
372 |         ssllib=parsed.ssl,
373 |         wait=parsed.wait
374 |     )
375 | 


--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
  1 | /**
  2 |    * Initializes 'addresses' dictionary and NativeFunctions.
  3 |    */
  4 | "use strict";
  5 | rpc.exports = {
  6 |   setssllib: function (name) {
  7 |     console.log("setSSLLib => " + name);
  8 |     libname = name;
  9 |     initializeGlobals();
 10 |     return;
 11 |   }
 12 | };
 13 | 
 14 | var addresses = {};
 15 | var SSL_get_fd = null;
 16 | var SSL_get_session = null;
 17 | var SSL_SESSION_get_id = null;
 18 | var getpeername = null;
 19 | var getsockname = null;
 20 | var ntohs = null;
 21 | var ntohl = null;
 22 | var SSLstackwrite = null;
 23 | var SSLstackread = null;
 24 | 
 25 | var libname = "*libssl*";
 26 | 
 27 | function uuid(len, radix) {
 28 |   var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
 29 |   var uuid = [], i;
 30 |   radix = radix || chars.length;
 31 | 
 32 |   if (len) {
 33 |     // Compact form
 34 |     for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
 35 |   } else {
 36 |     // rfc4122, version 4 form
 37 |     var r;
 38 | 
 39 |     // rfc4122 requires these characters
 40 |     uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
 41 |     uuid[14] = '4';
 42 | 
 43 |     // Fill in random data. At i==19 set the high bits of clock sequence as
 44 |     // per rfc4122, sec. 4.1.5
 45 |     for (i = 0; i < 36; i++) {
 46 |       if (!uuid[i]) {
 47 |         r = 0 | Math.random() * 16;
 48 |         uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
 49 |       }
 50 |     }
 51 |   }
 52 | 
 53 |   return uuid.join('');
 54 | }
 55 | function return_zero(args) {
 56 |   return 0;
 57 | }
 58 | function initializeGlobals() {
 59 |   var resolver = new ApiResolver("module");
 60 |   var exps = [
 61 |     [Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android
 62 |     [Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]]
 63 |   ];
 64 |   // console.log(exps)
 65 |   for (var i = 0; i < exps.length; i++) {
 66 |     var lib = exps[i][0];
 67 |     var names = exps[i][1];
 68 |     for (var j = 0; j < names.length; j++) {
 69 |       var name = names[j];
 70 |       // console.log("exports:" + lib + "!" + name)
 71 |       var matches = resolver.enumerateMatchesSync("exports:" + lib + "!" + name);
 72 |       if (matches.length == 0) {
 73 |         if (name == "SSL_get_fd") {
 74 |           addresses["SSL_get_fd"] = 0;
 75 |           continue;
 76 |         }
 77 |         throw "Could not find " + lib + "!" + name;
 78 |       }
 79 |       else if (matches.length != 1) {
 80 |         // Sometimes Frida returns duplicates.
 81 |         var address = 0;
 82 |         var s = "";
 83 |         var duplicates_only = true;
 84 |         for (var k = 0; k < matches.length; k++) {
 85 |           if (s.length != 0) {
 86 |             s += ", ";
 87 |           }
 88 |           s += matches[k].name + "@" + matches[k].address;
 89 |           if (address == 0) {
 90 |             address = matches[k].address;
 91 |           }
 92 |           else if (!address.equals(matches[k].address)) {
 93 |             duplicates_only = false;
 94 |           }
 95 |         }
 96 |         if (!duplicates_only) {
 97 |           throw "More than one match found for " + lib + "!" + name + ": " + s;
 98 |         }
 99 |       }
100 |       addresses[name] = matches[0].address;
101 |     }
102 |   }
103 |   if (addresses["SSL_get_fd"] == 0) {
104 |     SSL_get_fd = return_zero;
105 |   } else {
106 |     SSL_get_fd = new NativeFunction(addresses["SSL_get_fd"], "int", ["pointer"]);
107 |   }
108 |   SSL_get_session = new NativeFunction(addresses["SSL_get_session"], "pointer", ["pointer"]);
109 |   SSL_SESSION_get_id = new NativeFunction(addresses["SSL_SESSION_get_id"], "pointer", ["pointer", "pointer"]);
110 |   getpeername = new NativeFunction(addresses["getpeername"], "int", ["int", "pointer", "pointer"]);
111 |   getsockname = new NativeFunction(addresses["getsockname"], "int", ["int", "pointer", "pointer"]);
112 |   ntohs = new NativeFunction(addresses["ntohs"], "uint16", ["uint16"]);
113 |   ntohl = new NativeFunction(addresses["ntohl"], "uint32", ["uint32"]);
114 | }
115 | initializeGlobals();
116 | 
117 | function ipToNumber(ip) {
118 |   var num = 0;
119 |   if (ip == "") {
120 |     return num;
121 |   }
122 |   var aNum = ip.split(".");
123 |   if (aNum.length != 4) {
124 |     return num;
125 |   }
126 |   num += parseInt(aNum[0]) << 0;
127 |   num += parseInt(aNum[1]) << 8;
128 |   num += parseInt(aNum[2]) << 16;
129 |   num += parseInt(aNum[3]) << 24;
130 |   num = num >>> 0;//这个很关键,不然可能会出现负数的情况
131 |   return num;
132 | }
133 | 
134 | /**
135 |  * Returns a dictionary of a sockfd's "src_addr", "src_port", "dst_addr", and
136 |  * "dst_port".
137 |  * @param {int} sockfd The file descriptor of the socket to inspect.
138 |  * @param {boolean} isRead If true, the context is an SSL_read call. If
139 |  *     false, the context is an SSL_write call.
140 |  * @return {dict} Dictionary of sockfd's "src_addr", "src_port", "dst_addr",
141 |  *     and "dst_port".
142 |  */
143 | function getPortsAndAddresses(sockfd, isRead) {
144 |   var message = {};
145 |   var src_dst = ["src", "dst"];
146 |   for (var i = 0; i < src_dst.length; i++) {
147 |     if ((src_dst[i] == "src") ^ isRead) {
148 |       var sockAddr = Socket.localAddress(sockfd)
149 |     }
150 |     else {
151 |       var sockAddr = Socket.peerAddress(sockfd)
152 |     }
153 |     if (sockAddr == null) {
154 |       // 网络超时or其他原因可能导致socket被关闭
155 |       message[src_dst[i] + "_port"] = 0
156 |       message[src_dst[i] + "_addr"] = 0
157 |     } else {
158 |       message[src_dst[i] + "_port"] = (sockAddr.port & 0xFFFF)
159 |       message[src_dst[i] + "_addr"] = ntohl(ipToNumber(sockAddr.ip.split(":").pop()))
160 |     }
161 |   }
162 |   return message;
163 | }
164 | /**
165 |  * Get the session_id of SSL object and return it as a hex string.
166 |  * @param {!NativePointer} ssl A pointer to an SSL object.
167 |  * @return {dict} A string representing the session_id of the SSL object's
168 |  *     SSL_SESSION. For example,
169 |  *     "59FD71B7B90202F359D89E66AE4E61247954E28431F6C6AC46625D472FF76336".
170 |  */
171 | function getSslSessionId(ssl) {
172 |   var session = SSL_get_session(ssl);
173 |   if (session == 0) {
174 |     return 0;
175 |   }
176 |   var len = Memory.alloc(4);
177 |   var p = SSL_SESSION_get_id(session, len);
178 |   len = Memory.readU32(len);
179 |   var session_id = "";
180 |   for (var i = 0; i < len; i++) {
181 |     // Read a byte, convert it to a hex string (0xAB ==> "AB"), and append
182 |     // it to session_id.
183 |     session_id +=
184 |       ("0" + Memory.readU8(p.add(i)).toString(16).toUpperCase()).substr(-2);
185 |   }
186 |   return session_id;
187 | }
188 | 
189 | Interceptor.attach(addresses["SSL_read"],
190 |   {
191 |     onEnter: function (args) {
192 |       var message = getPortsAndAddresses(SSL_get_fd(args[0]), true);
193 |       message["ssl_session_id"] = getSslSessionId(args[0]);
194 |       message["function"] = "SSL_read";
195 |       message["stack"] = SSLstackread;
196 |       this.message = message;
197 |       this.buf = args[1];
198 |     },
199 |     onLeave: function (retval) {
200 |       retval |= 0; // Cast retval to 32-bit integer.
201 |       if (retval <= 0) {
202 |         return;
203 |       }
204 |       send(this.message, Memory.readByteArray(this.buf, retval));
205 |     }
206 |   });
207 | 
208 | Interceptor.attach(addresses["SSL_write"],
209 |   {
210 |     onEnter: function (args) {
211 |       var message = getPortsAndAddresses(SSL_get_fd(args[0]), false);
212 |       message["ssl_session_id"] = getSslSessionId(args[0]);
213 |       message["function"] = "SSL_write";
214 |       message["stack"] = SSLstackwrite;
215 |       send(message, Memory.readByteArray(args[1], parseInt(args[2])));
216 |     },
217 |     onLeave: function (retval) {
218 |     }
219 |   });
220 | 
221 | if (Java.available) {
222 |   Java.perform(function () {
223 |     function storeP12(pri, p7, p12Path, p12Password) {
224 |       var X509Certificate = Java.use("java.security.cert.X509Certificate")
225 |       var p7X509 = Java.cast(p7, X509Certificate);
226 |       var chain = Java.array("java.security.cert.X509Certificate", [p7X509])
227 |       var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC");
228 |       ks.load(null, null);
229 |       ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);
230 |       try {
231 |         var out = Java.use("java.io.FileOutputStream").$new(p12Path);
232 |         ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())
233 |       } catch (exp) {
234 |         console.log(exp)
235 |       }
236 |     }
237 |     //在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
238 |     Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () {
239 |       var result = this.getPrivateKey()
240 |       var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
241 |       storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
242 |       var message = {};
243 |       message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + '   pwd: r0ysue';
244 |       message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
245 |       var data = Memory.alloc(1);
246 |       send(message, Memory.readByteArray(data, 1))
247 |       return result;
248 |     }
249 |     Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () {
250 |       var result = this.getCertificateChain()
251 |       var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
252 |       storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
253 |       var message = {};
254 |       message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + '   pwd: r0ysue';
255 |       message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
256 |       var data = Memory.alloc(1);
257 |       send(message, Memory.readByteArray(data, 1))
258 |       return result;
259 |     }
260 | 
261 |     //SSLpinning helper 帮助定位证书绑定的关键代码a
262 |     Java.use("java.io.File").$init.overload('java.io.File', 'java.lang.String').implementation = function (file, cert) {
263 |       var result = this.$init(file, cert)
264 |       var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
265 |       if (file.getPath().indexOf("cacert") >= 0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted") >= 0) {
266 |         var message = {};
267 |         message["function"] = "SSLpinning position locator => " + file.getPath() + " " + cert;
268 |         message["stack"] = stack;
269 |         var data = Memory.alloc(1);
270 |         send(message, Memory.readByteArray(data, 1))
271 |       }
272 |       return result;
273 |     }
274 | 
275 | 
276 |     Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {
277 |       var result = this.socketWrite0(fd, bytearry, offset, byteCount);
278 |       var message = {};
279 |       message["function"] = "HTTP_send";
280 |       message["ssl_session_id"] = "";
281 |       message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
282 |       message["src_port"] = parseInt(this.socket.value.getLocalPort().toString());
283 |       message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
284 |       message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
285 |       message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
286 |       var ptr = Memory.alloc(byteCount);
287 |       for (var i = 0; i < byteCount; ++i)
288 |         Memory.writeS8(ptr.add(i), bytearry[offset + i]);
289 |       send(message, Memory.readByteArray(ptr, byteCount))
290 |       return result;
291 |     }
292 |     Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {
293 |       var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
294 |       var message = {};
295 |       message["function"] = "HTTP_recv";
296 |       message["ssl_session_id"] = "";
297 |       message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
298 |       message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
299 |       message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
300 |       message["dst_port"] = parseInt(this.socket.value.getLocalPort());
301 |       message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
302 |       if (result > 0) {
303 |         var ptr = Memory.alloc(result);
304 |         for (var i = 0; i < result; ++i)
305 |           Memory.writeS8(ptr.add(i), bytearry[offset + i]);
306 |         send(message, Memory.readByteArray(ptr, result))
307 |       }
308 |       return result;
309 |     }
310 | 
311 |     if (parseFloat(Java.androidVersion)  > 8) {
312 |       Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
313 |         var result = this.write(bytearry, int1, int2);
314 |         SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
315 |         return result;
316 |       }
317 |       Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
318 |         var result = this.read(bytearry, int1, int2);
319 |         SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
320 |         return result;
321 |       }
322 |     }
323 |     else {
324 |       Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
325 |         var result = this.write(bytearry, int1, int2);
326 |         SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
327 |         return result;
328 |       }
329 |       Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
330 |         var result = this.read(bytearry, int1, int2);
331 |         SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
332 |         return result;
333 |       }
334 | 
335 |     }
336 |   }
337 | 
338 |   )
339 | }
340 | 


--------------------------------------------------------------------------------