Compare commits
80 Commits
9d2464ee9e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 13d89b020d | |||
| 34735c3e0e | |||
| 866701b499 | |||
| f47e9cb306 | |||
| 8c4cd61081 | |||
| cfefd337b9 | |||
| af1a9e1859 | |||
| 6c7278a064 | |||
| 58c81f32e5 | |||
| 67623951ce | |||
| 8960406388 | |||
| 51bf1e5a6f | |||
| 0e75201906 | |||
| 8aa5351ccc | |||
| 1e80a8067b | |||
| df7b021ea0 | |||
| 86f1a339d0 | |||
| b8e653e57d | |||
| 9ec56ced34 | |||
| e949fdc3e5 | |||
| 7167780f0f | |||
| dd6a0f4018 | |||
| 64e5f9c6d9 | |||
| f16dd8cae1 | |||
| e358abc840 | |||
| 054d36b04d | |||
| 424ff95d2b | |||
| 8405b06e9a | |||
| 115626622b | |||
| 7a34565a64 | |||
| 9de27d332d | |||
| deb6e8fb4c | |||
| fc56cd8c54 | |||
| 7dc4eb24fd | |||
| 358c51a054 | |||
| db7b6cb54a | |||
| b1b381b7a6 | |||
| 7dd5c0d661 | |||
| 4b174c9f3e | |||
| 0892550341 | |||
| 1613f15825 | |||
| 0370063b48 | |||
| 239f2b1de4 | |||
| df79d971f4 | |||
| b1164af03a | |||
| c0a5ef2486 | |||
| 75d28434ee | |||
| aec778ff41 | |||
| 21aa528f08 | |||
| 29fc054c02 | |||
| 9e8761d7a4 | |||
| dd3e023690 | |||
| 64d25ac27d | |||
| 4cb52cd56e | |||
| 0226485c31 | |||
| 19333bc43a | |||
| e74d6554e9 | |||
| 528b0d8885 | |||
| 23640b607a | |||
| 649dd5ac89 | |||
| 27631b814a | |||
| c8b014e457 | |||
| b46444dea2 | |||
| 8fc63ad9c0 | |||
| 26622767e6 | |||
| 2311f5ad29 | |||
| 1c199de379 | |||
| c8e27de06b | |||
| 132bc23c8c | |||
| 0059733ffd | |||
| d8e7976afc | |||
| cf74259b5e | |||
| e0f14d46ea | |||
| 1d88394e2b | |||
| c0f2b7110c | |||
| 017ef1ef56 | |||
| e9c1de8fe5 | |||
| d251f3e86c | |||
| 7e54e08c90 | |||
| ff00478296 |
4
.idea/hook.iml
generated
4
.idea/hook.iml
generated
@@ -3,8 +3,10 @@
|
|||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv 3.11" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv 3.9" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="jdk" jdkName="Python 3.9 (hook) (2)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (hook)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (hook) (2)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
65
frida抓包.md
Normal file
65
frida抓包.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# frida抓包
|
||||||
|
|
||||||
|
## 环境和工具准备:
|
||||||
|
|
||||||
|
- python 3.x
|
||||||
|
- r0capture
|
||||||
|
- frida / frida-tools
|
||||||
|
- apkshell
|
||||||
|
|
||||||
|
## r0capture
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/r0ysue/r0capture
|
||||||
|
```
|
||||||
|
|
||||||
|
## frida
|
||||||
|
|
||||||
|
frida-server下载地址
|
||||||
|
|
||||||
|
```
|
||||||
|
https://github.com/frida/frida/releases
|
||||||
|
```
|
||||||
|
|
||||||
|
虚拟环境安装frida和frida- tools
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install frida
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install frida-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
使用Frida查看包进程
|
||||||
|
|
||||||
|
```shell
|
||||||
|
frida-ps -U | grep "包名"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行 r0capture.py
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python r0capture.py -U 前面记录的目标应用包名 -p xxx.pcap
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 r0capture/r0capture.py -U com.vmall.client -p com.vmall.client.pcap
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
其中 -p 参数用来保存抓包结果,.pcap 是数据报存储格式,包括 Wireshark 在内的很多主流抓包软件都可以生成或者导入 pcap 数据包并分析
|
||||||
|
|
||||||
|
如果中途提示 hexdump 名称错误,pip 安装一下即可
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install hexdump
|
||||||
|
```
|
||||||
|
|
||||||
|
# hook appsflyer conversions
|
||||||
|
|
||||||
|
```
|
||||||
|
frida -U -l <jshook代码> -f <packageName>
|
||||||
|
```
|
||||||
|
|
||||||
76
main.py
Normal file
76
main.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import frida
|
||||||
|
import modules.command
|
||||||
|
import modules.files_utils
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
FIRST_WRITE = True # 全局变量,用于跟踪是否是首次写入
|
||||||
|
|
||||||
|
def on_message(message, data):
|
||||||
|
print(message)
|
||||||
|
# modules.files_utils.write_log(message)
|
||||||
|
# print(message)
|
||||||
|
# if message['type'] == 'send':
|
||||||
|
# print(message['payload'])
|
||||||
|
|
||||||
|
def attach_method(is_spawn):
|
||||||
|
if type(is_spawn) == bool:
|
||||||
|
if is_spawn:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"is_spawn type error,please check is_spawn type.")
|
||||||
|
|
||||||
|
|
||||||
|
def main(package_name,script_path,is_spawn):
|
||||||
|
# 从Python发送数据到Frida的JavaScript脚本
|
||||||
|
print(f"script_path: {script_path}")
|
||||||
|
js_code = modules.files_utils.read_javascript(script_path)
|
||||||
|
# print(js_code)
|
||||||
|
modules.command.start_frida()
|
||||||
|
# modules.command.clearCache(package_name)
|
||||||
|
# 连接到USB设备
|
||||||
|
device = frida.get_usb_device()
|
||||||
|
print(device)
|
||||||
|
if attach_method(is_spawn):
|
||||||
|
pid = device.spawn(package_name)
|
||||||
|
print(f"进程pid: {pid}")
|
||||||
|
process = device.attach(pid)
|
||||||
|
script = process.create_script(js_code)
|
||||||
|
# script.on("message", on_message)
|
||||||
|
script.load()
|
||||||
|
# data_to_send = {'data': 'Hello from Python!'}
|
||||||
|
# script.post({'type': 'input_data', 'payload': data_to_send})
|
||||||
|
device.resume(pid) # 加载完脚本后, 恢复进程运行
|
||||||
|
sys.stdin.read()
|
||||||
|
else:
|
||||||
|
# 列出设备上的所有进程
|
||||||
|
pid = None
|
||||||
|
processes = device.enumerate_processes()
|
||||||
|
for process in processes:
|
||||||
|
if process.name == attach_process_name:
|
||||||
|
pid = process.pid
|
||||||
|
print(f"pid: {process.pid},App Name: {process.name}")
|
||||||
|
# 如果你想附加到一个特定的进程并注入一个脚本:
|
||||||
|
if pid is not None:
|
||||||
|
session = device.attach(pid)
|
||||||
|
script = session.create_script(js_code)
|
||||||
|
script.on('message', on_message)
|
||||||
|
script.load()
|
||||||
|
else:
|
||||||
|
print(f"get process error")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 是否以spawn模式运行
|
||||||
|
is_spawn = True
|
||||||
|
# 目标进程名
|
||||||
|
attach_process_name = "百度网盘"
|
||||||
|
# 目标包名
|
||||||
|
package_name = "com.baidu.netdisk"
|
||||||
|
# 注入的脚本路径
|
||||||
|
# script_path = "scripts/hook_conversions.js"
|
||||||
|
script_path = "scripts/baidunetdisk.js"
|
||||||
|
main(package_name,script_path, is_spawn)
|
||||||
0
modules/__init__.py
Normal file
0
modules/__init__.py
Normal file
98
modules/command.py
Normal file
98
modules/command.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
from modules.files_utils import get_path
|
||||||
|
|
||||||
|
|
||||||
|
def run_adb_command(command_list):
|
||||||
|
process = subprocess.Popen(command_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
output = stdout.decode().strip()
|
||||||
|
status_code = process.returncode
|
||||||
|
error = stderr.decode().strip()
|
||||||
|
return output, status_code, error
|
||||||
|
def start_frida():
|
||||||
|
if is_frida_running():
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
output, status_code, error = run_adb_command(['adb', 'shell', 'su', '-c', '/data/local/tmp/frica'])
|
||||||
|
if status_code == 0:
|
||||||
|
print(output)
|
||||||
|
logging.info(f"start frida output: {output}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(error)
|
||||||
|
logging.error(f"start error,error:{error}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_frida_running():
|
||||||
|
output, status_code, error = run_adb_command(['adb', 'shell', 'ps', '|', 'grep', 'frica'])
|
||||||
|
print(output)
|
||||||
|
return 'frica' in output
|
||||||
|
|
||||||
|
|
||||||
|
def get_main_activity_for_package(package_name):
|
||||||
|
output, status_code, error = run_adb_command(['adb', 'shell', 'dumpsys', 'package', package_name])
|
||||||
|
if status_code != 0:
|
||||||
|
print(f"Error getting main activity: {error}")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# print(f"output: {output},output type: {type(output)}")
|
||||||
|
return find_mainActivity(output,package_name)
|
||||||
|
|
||||||
|
|
||||||
|
def find_mainActivity(output,package_name):
|
||||||
|
activity = []
|
||||||
|
start_append = False
|
||||||
|
lines = [line.strip() for line in output.split("\n")]
|
||||||
|
for line in lines:
|
||||||
|
if "Activity Resolver Table:" in line:
|
||||||
|
activity.append(line)
|
||||||
|
if "Non-Data Actions:" in line:
|
||||||
|
activity.append(line)
|
||||||
|
start_append = True
|
||||||
|
elif "android.intent.category.LAUNCHER" in line:
|
||||||
|
activity.append(line)
|
||||||
|
break
|
||||||
|
elif start_append == True:
|
||||||
|
activity.append(line)
|
||||||
|
for main_ac in activity:
|
||||||
|
if package_name in main_ac:
|
||||||
|
tmp = main_ac.split()
|
||||||
|
print(tmp[1])
|
||||||
|
return tmp[1]
|
||||||
|
# lines = output.split('\n')
|
||||||
|
# print(f"lines: \n{lines},lines type: {type(lines)}")
|
||||||
|
# last_line_indent = 0
|
||||||
|
# for line in lines:
|
||||||
|
# stripped = line.lstrip()
|
||||||
|
# indent = len(line) - len(stripped)
|
||||||
|
# print(indent)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def clearCache(package_name):
|
||||||
|
if stopApp(package_name):
|
||||||
|
output, status_code, error = run_adb_command(['adb', 'shell', 'pm', 'clear', package_name])
|
||||||
|
if status_code == 0:
|
||||||
|
print(f"clear cache status_code: {status_code}\noutput: {output}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(error)
|
||||||
|
print(f"clear cache error: {error}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("stopApp error")
|
||||||
|
|
||||||
|
def stopApp(package_name):
|
||||||
|
print(f"强行停止{package_name}")
|
||||||
|
output, status_code, error = run_adb_command(['adb', 'shell', 'am', 'force-stop', package_name])
|
||||||
|
if status_code == 0:
|
||||||
|
print(f"status_code: {status_code}\noutput: {output}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(error)
|
||||||
|
logging.error(f"stop APP error: {error}")
|
||||||
|
return False
|
||||||
|
|
||||||
23
modules/files_utils.py
Normal file
23
modules/files_utils.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
# 获取当前Python脚本的绝对路径
|
||||||
|
def get_path(script_name):
|
||||||
|
script_directory = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
parent_directory = os.path.dirname(script_directory)
|
||||||
|
# 使用os.path.join构建hook_conversions.js的完整路径
|
||||||
|
script_path = os.path.join(parent_directory,script_name)
|
||||||
|
print(script_path)
|
||||||
|
return script_path
|
||||||
|
|
||||||
|
def read_javascript(script_path):
|
||||||
|
with open(script_path, "r") as file:
|
||||||
|
script_code = file.read()
|
||||||
|
return script_code
|
||||||
|
def write_log(messages):
|
||||||
|
global FIRST_WRITE
|
||||||
|
print(f"FIRST_WRITE: {FIRST_WRITE}")
|
||||||
|
with open("frida_log.log", "a") as log_file:
|
||||||
|
if FIRST_WRITE: # 如果是首次写入
|
||||||
|
log_file.write("\n\n\n") # 空出三行
|
||||||
|
FIRST_WRITE = False # 更新状态,表明已经写过了
|
||||||
|
log_file.write(str(messages) + "\n")
|
||||||
479
r0capture/myhexdump.py
Normal file
479
r0capture/myhexdump.py
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# !/usr/bin/env python
|
||||||
|
# -*- coding: latin-1 -*-
|
||||||
|
|
||||||
|
# <-- removing this magic comment breaks Python 3.4 on Windows
|
||||||
|
"""
|
||||||
|
1. Dump binary data to the following text format:
|
||||||
|
|
||||||
|
00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump]....
|
||||||
|
00000010: 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
|
||||||
|
|
||||||
|
It is similar to the one used by:
|
||||||
|
Scapy
|
||||||
|
00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump]....
|
||||||
|
00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
|
||||||
|
|
||||||
|
Far Manager
|
||||||
|
000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump]
|
||||||
|
000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 AA BB CC DD EE FF ?"3DUfwª»ÌÝîÿ
|
||||||
|
|
||||||
|
|
||||||
|
2. Restore binary data from the formats above as well
|
||||||
|
as from less exotic strings of raw hex
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = '3.3'
|
||||||
|
__author__ = 'anatoly techtonik <techtonik@gmail.com>'
|
||||||
|
__license__ = 'Public Domain'
|
||||||
|
|
||||||
|
__history__ = \
|
||||||
|
"""
|
||||||
|
3.3 (2015-01-22)
|
||||||
|
* accept input from sys.stdin if "-" is specified
|
||||||
|
for both dump and restore (issue #1)
|
||||||
|
* new normalize_py() helper to set sys.stdout to
|
||||||
|
binary mode on Windows
|
||||||
|
|
||||||
|
3.2 (2015-07-02)
|
||||||
|
* hexdump is now packaged as .zip on all platforms
|
||||||
|
(on Linux created archive was tar.gz)
|
||||||
|
* .zip is executable! try `python hexdump-3.2.zip`
|
||||||
|
* dump() now accepts configurable separator, patch
|
||||||
|
by Ian Land (PR #3)
|
||||||
|
|
||||||
|
3.1 (2014-10-20)
|
||||||
|
* implemented workaround against mysterious coding
|
||||||
|
issue with Python 3 (see revision 51302cf)
|
||||||
|
* fix Python 3 installs for systems where UTF-8 is
|
||||||
|
not default (Windows), thanks to George Schizas
|
||||||
|
(the problem was caused by reading of README.txt)
|
||||||
|
|
||||||
|
3.0 (2014-09-07)
|
||||||
|
* remove unused int2byte() helper
|
||||||
|
* add dehex(text) helper to convert hex string
|
||||||
|
to binary data
|
||||||
|
* add 'size' argument to dump() helper to specify
|
||||||
|
length of chunks
|
||||||
|
|
||||||
|
2.0 (2014-02-02)
|
||||||
|
* add --restore option to command line mode to get
|
||||||
|
binary data back from hex dump
|
||||||
|
* support saving test output with `--test logfile`
|
||||||
|
* restore() from hex strings without spaces
|
||||||
|
* restore() now raises TypeError if input data is
|
||||||
|
not string
|
||||||
|
* hexdump() and dumpgen() now don't return unicode
|
||||||
|
strings in Python 2.x when generator is requested
|
||||||
|
|
||||||
|
1.0 (2013-12-30)
|
||||||
|
* length of address is reduced from 10 to 8
|
||||||
|
* hexdump() got new 'result' keyword argument, it
|
||||||
|
can be either 'print', 'generator' or 'return'
|
||||||
|
* actual dumping logic is now in new dumpgen()
|
||||||
|
generator function
|
||||||
|
* new dump(binary) function that takes binary data
|
||||||
|
and returns string like "66 6F 72 6D 61 74"
|
||||||
|
* new genchunks(mixed, size) function that chunks
|
||||||
|
both sequences and file like objects
|
||||||
|
|
||||||
|
0.5 (2013-06-10)
|
||||||
|
* hexdump is now also a command line utility (no
|
||||||
|
restore yet)
|
||||||
|
|
||||||
|
0.4 (2013-06-09)
|
||||||
|
* fix installation with Python 3 for non English
|
||||||
|
versions of Windows, thanks to George Schizas
|
||||||
|
|
||||||
|
0.3 (2013-04-29)
|
||||||
|
* fully Python 3 compatible
|
||||||
|
|
||||||
|
0.2 (2013-04-28)
|
||||||
|
* restore() to recover binary data from a hex dump in
|
||||||
|
native, Far Manager and Scapy text formats (others
|
||||||
|
might work as well)
|
||||||
|
* restore() is Python 3 compatible
|
||||||
|
|
||||||
|
0.1 (2013-04-28)
|
||||||
|
* working hexdump() function for Python 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
import binascii # binascii is required for Python 3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# --- constants
|
||||||
|
PY3K = sys.version_info >= (3, 0)
|
||||||
|
|
||||||
|
|
||||||
|
# --- workaround against Python consistency issues
|
||||||
|
def normalize_py():
|
||||||
|
''' Problem 001 - sys.stdout in Python is by default opened in
|
||||||
|
text mode, and writes to this stdout produce corrupted binary
|
||||||
|
data on Windows
|
||||||
|
|
||||||
|
python -c "import sys; sys.stdout.write('_\n_')" > file
|
||||||
|
python -c "print(repr(open('file', 'rb').read()))"
|
||||||
|
'''
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# set sys.stdout to binary mode on Windows
|
||||||
|
import os, msvcrt
|
||||||
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
||||||
|
|
||||||
|
|
||||||
|
# --- - chunking helpers
|
||||||
|
def chunks(seq, size):
|
||||||
|
'''Generator that cuts sequence (bytes, memoryview, etc.)
|
||||||
|
into chunks of given size. If `seq` length is not multiply
|
||||||
|
of `size`, the lengh of the last chunk returned will be
|
||||||
|
less than requested.
|
||||||
|
|
||||||
|
>>> list( chunks([1,2,3,4,5,6,7], 3) )
|
||||||
|
[[1, 2, 3], [4, 5, 6], [7]]
|
||||||
|
'''
|
||||||
|
d, m = divmod(len(seq), size)
|
||||||
|
for i in range(d):
|
||||||
|
yield seq[i * size:(i + 1) * size]
|
||||||
|
if m:
|
||||||
|
yield seq[d * size:]
|
||||||
|
|
||||||
|
|
||||||
|
def chunkread(f, size):
|
||||||
|
'''Generator that reads from file like object. May return less
|
||||||
|
data than requested on the last read.'''
|
||||||
|
c = f.read(size)
|
||||||
|
while len(c):
|
||||||
|
yield c
|
||||||
|
c = f.read(size)
|
||||||
|
|
||||||
|
|
||||||
|
def genchunks(mixed, size):
|
||||||
|
'''Generator to chunk binary sequences or file like objects.
|
||||||
|
The size of the last chunk returned may be less than
|
||||||
|
requested.'''
|
||||||
|
if hasattr(mixed, 'read'):
|
||||||
|
return chunkread(mixed, size)
|
||||||
|
else:
|
||||||
|
return chunks(mixed, size)
|
||||||
|
|
||||||
|
|
||||||
|
# --- - /chunking helpers
|
||||||
|
|
||||||
|
|
||||||
|
def dehex(hextext):
|
||||||
|
"""
|
||||||
|
Convert from hex string to binary data stripping
|
||||||
|
whitespaces from `hextext` if necessary.
|
||||||
|
"""
|
||||||
|
if PY3K:
|
||||||
|
return bytes.fromhex(hextext)
|
||||||
|
else:
|
||||||
|
hextext = "".join(hextext.split())
|
||||||
|
return hextext.decode('hex')
|
||||||
|
|
||||||
|
|
||||||
|
def dump(binary, size=2, sep=' '):
|
||||||
|
'''
|
||||||
|
Convert binary data (bytes in Python 3 and str in
|
||||||
|
Python 2) to hex string like '00 DE AD BE EF'.
|
||||||
|
`size` argument specifies length of text chunks
|
||||||
|
and `sep` sets chunk separator.
|
||||||
|
'''
|
||||||
|
hexstr = binascii.hexlify(binary)
|
||||||
|
if PY3K:
|
||||||
|
hexstr = hexstr.decode('ascii')
|
||||||
|
return sep.join(chunks(hexstr.upper(), size))
|
||||||
|
|
||||||
|
|
||||||
|
def dumpgen(data, only_str):
|
||||||
|
'''
|
||||||
|
Generator that produces strings:
|
||||||
|
|
||||||
|
'00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................'
|
||||||
|
'''
|
||||||
|
generator = genchunks(data, 16)
|
||||||
|
for addr, d in enumerate(generator):
|
||||||
|
line = ""
|
||||||
|
if not only_str:
|
||||||
|
# 00000000:
|
||||||
|
line = '%08X: ' % (addr * 16)
|
||||||
|
# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
dumpstr = dump(d)
|
||||||
|
line += dumpstr[:8 * 3]
|
||||||
|
if len(d) > 8: # insert separator if needed
|
||||||
|
line += ' ' + dumpstr[8 * 3:]
|
||||||
|
# ................
|
||||||
|
# calculate indentation, which may be different for the last line
|
||||||
|
pad = 2
|
||||||
|
if len(d) < 16:
|
||||||
|
pad += 3 * (16 - len(d))
|
||||||
|
if len(d) <= 8:
|
||||||
|
pad += 1
|
||||||
|
line += ' ' * pad
|
||||||
|
|
||||||
|
for byte in d:
|
||||||
|
# printable ASCII range 0x20 to 0x7E
|
||||||
|
if not PY3K:
|
||||||
|
byte = ord(byte)
|
||||||
|
if 0x20 <= byte <= 0x7E:
|
||||||
|
line += chr(byte)
|
||||||
|
else:
|
||||||
|
line += '.'
|
||||||
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
def hexdump(data, result='print', only_str=False):
|
||||||
|
'''
|
||||||
|
Transform binary data to the hex dump text format:
|
||||||
|
|
||||||
|
00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||||
|
|
||||||
|
[x] data argument as a binary string
|
||||||
|
[x] data argument as a file like object
|
||||||
|
|
||||||
|
Returns result depending on the `result` argument:
|
||||||
|
'print' - prints line by line
|
||||||
|
'return' - returns single string
|
||||||
|
'generator' - returns generator that produces lines
|
||||||
|
'''
|
||||||
|
if PY3K and type(data) == str:
|
||||||
|
raise TypeError('Abstract unicode data (expected bytes sequence)')
|
||||||
|
|
||||||
|
gen = dumpgen(data, only_str=only_str)
|
||||||
|
if result == 'generator':
|
||||||
|
return gen
|
||||||
|
elif result == 'return':
|
||||||
|
return '\n'.join(gen)
|
||||||
|
elif result == 'print':
|
||||||
|
for line in gen:
|
||||||
|
print(line)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown value of `result` argument')
|
||||||
|
|
||||||
|
|
||||||
|
def restore(dump):
|
||||||
|
'''
|
||||||
|
Restore binary data from a hex dump.
|
||||||
|
[x] dump argument as a string
|
||||||
|
[ ] dump argument as a line iterator
|
||||||
|
|
||||||
|
Supported formats:
|
||||||
|
[x] hexdump.hexdump
|
||||||
|
[x] Scapy
|
||||||
|
[x] Far Manager
|
||||||
|
'''
|
||||||
|
minhexwidth = 2 * 16 # minimal width of the hex part - 00000... style
|
||||||
|
bytehexwidth = 3 * 16 - 1 # min width for a bytewise dump - 00 00 ... style
|
||||||
|
|
||||||
|
result = bytes() if PY3K else ''
|
||||||
|
if type(dump) != str:
|
||||||
|
raise TypeError('Invalid data for restore')
|
||||||
|
|
||||||
|
text = dump.strip() # ignore surrounding empty lines
|
||||||
|
for line in text.split('\n'):
|
||||||
|
# strip address part
|
||||||
|
addrend = line.find(':')
|
||||||
|
if 0 < addrend < minhexwidth: # : is not in ascii part
|
||||||
|
line = line[addrend + 1:]
|
||||||
|
line = line.lstrip()
|
||||||
|
# check dump type
|
||||||
|
if line[2] == ' ': # 00 00 00 ... type of dump
|
||||||
|
# check separator
|
||||||
|
sepstart = (2 + 1) * 7 + 2 # ('00'+' ')*7+'00'
|
||||||
|
sep = line[sepstart:sepstart + 3]
|
||||||
|
if sep[:2] == ' ' and sep[2:] != ' ': # ...00 00 00 00...
|
||||||
|
hexdata = line[:bytehexwidth + 1]
|
||||||
|
elif sep[2:] == ' ': # ...00 00 | 00 00... - Far Manager
|
||||||
|
hexdata = line[:sepstart] + line[sepstart + 3:bytehexwidth + 2]
|
||||||
|
else: # ...00 00 00 00... - Scapy, no separator
|
||||||
|
hexdata = line[:bytehexwidth]
|
||||||
|
line = hexdata
|
||||||
|
result += dehex(line)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def runtest(logfile=None):
|
||||||
|
'''Run hexdump tests. Requires hexfile.bin to be in the same
|
||||||
|
directory as hexdump.py itself'''
|
||||||
|
|
||||||
|
class TeeOutput(object):
|
||||||
|
def __init__(self, stream1, stream2):
|
||||||
|
self.outputs = [stream1, stream2]
|
||||||
|
|
||||||
|
# -- methods from sys.stdout / sys.stderr
|
||||||
|
def write(self, data):
|
||||||
|
for stream in self.outputs:
|
||||||
|
if PY3K:
|
||||||
|
if 'b' in stream.mode:
|
||||||
|
data = data.encode('utf-8')
|
||||||
|
stream.write(data)
|
||||||
|
stream.flush()
|
||||||
|
|
||||||
|
def tell(self):
|
||||||
|
raise IOError
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
for stream in self.outputs:
|
||||||
|
stream.flush()
|
||||||
|
# --/ sys.stdout
|
||||||
|
|
||||||
|
if logfile:
|
||||||
|
openlog = open(logfile, 'wb')
|
||||||
|
# copy stdout and stderr streams to log file
|
||||||
|
savedstd = sys.stderr, sys.stdout
|
||||||
|
sys.stderr = TeeOutput(sys.stderr, openlog)
|
||||||
|
sys.stdout = TeeOutput(sys.stdout, openlog)
|
||||||
|
|
||||||
|
def echo(msg, linefeed=True):
|
||||||
|
sys.stdout.write(msg)
|
||||||
|
if linefeed:
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
|
||||||
|
expected = '''\
|
||||||
|
00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump]....
|
||||||
|
00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........\
|
||||||
|
'''
|
||||||
|
|
||||||
|
# get path to hexfile.bin
|
||||||
|
# this doesn't work from .zip
|
||||||
|
# import os.path as osp
|
||||||
|
# hexfile = osp.dirname(osp.abspath(__file__)) + '/hexfile.bin'
|
||||||
|
# this doesn't work either
|
||||||
|
# hexfile = osp.dirname(sys.modules[__name__].__file__) + '/hexfile.bin'
|
||||||
|
# this works
|
||||||
|
import pkgutil
|
||||||
|
bin = pkgutil.get_data('hexdump', 'data/hexfile.bin')
|
||||||
|
|
||||||
|
# varios length of input data
|
||||||
|
hexdump(b'zzzz' * 12)
|
||||||
|
hexdump(b'o' * 17)
|
||||||
|
hexdump(b'p' * 24)
|
||||||
|
hexdump(b'q' * 26)
|
||||||
|
# allowable character set filter
|
||||||
|
hexdump(b'line\nfeed\r\ntest')
|
||||||
|
hexdump(b'\x00\x00\x00\x5B\x68\x65\x78\x64\x75\x6D\x70\x5D\x00\x00\x00\x00'
|
||||||
|
b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\x0A\xBB\xCC\xDD\xEE\xFF')
|
||||||
|
print('---')
|
||||||
|
# dumping file-like binary object to screen (default behavior)
|
||||||
|
hexdump(bin)
|
||||||
|
print('return output')
|
||||||
|
hexout = hexdump(bin, result='return')
|
||||||
|
assert hexout == expected, 'returned hex didn\'t match'
|
||||||
|
print('return generator')
|
||||||
|
hexgen = hexdump(bin, result='generator')
|
||||||
|
assert next(hexgen) == expected.split('\n')[0], 'hex generator 1 didn\'t match'
|
||||||
|
assert next(hexgen) == expected.split('\n')[1], 'hex generator 2 didn\'t match'
|
||||||
|
|
||||||
|
# binary restore test
|
||||||
|
bindata = restore(
|
||||||
|
'''
|
||||||
|
00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump]....
|
||||||
|
00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........
|
||||||
|
''')
|
||||||
|
echo('restore check ', linefeed=False)
|
||||||
|
assert bin == bindata, 'restore check failed'
|
||||||
|
echo('passed')
|
||||||
|
|
||||||
|
far = \
|
||||||
|
'''
|
||||||
|
000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump]
|
||||||
|
000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 0A BB CC DD EE FF ?"3DUfwª»ÌÝîÿ
|
||||||
|
'''
|
||||||
|
echo('restore far format ', linefeed=False)
|
||||||
|
assert bin == restore(far), 'far format check failed'
|
||||||
|
echo('passed')
|
||||||
|
|
||||||
|
scapy = '''\
|
||||||
|
00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump]....
|
||||||
|
00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........
|
||||||
|
'''
|
||||||
|
echo('restore scapy format ', linefeed=False)
|
||||||
|
assert bin == restore(scapy), 'scapy format check failed'
|
||||||
|
echo('passed')
|
||||||
|
|
||||||
|
if not PY3K:
|
||||||
|
assert restore('5B68657864756D705D') == '[hexdump]', 'no space check failed'
|
||||||
|
assert dump('\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e'
|
||||||
|
else:
|
||||||
|
assert restore('5B68657864756D705D') == b'[hexdump]', 'no space check failed'
|
||||||
|
assert dump(b'\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e'
|
||||||
|
|
||||||
|
print('---[test file hexdumping]---')
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
hexfile = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
try:
|
||||||
|
hexfile.write(bin)
|
||||||
|
hexfile.close()
|
||||||
|
hexdump(open(hexfile.name, 'rb'))
|
||||||
|
finally:
|
||||||
|
os.remove(hexfile.name)
|
||||||
|
if logfile:
|
||||||
|
sys.stderr, sys.stdout = savedstd
|
||||||
|
openlog.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
from optparse import OptionParser
|
||||||
|
parser = OptionParser(usage='''
|
||||||
|
%prog [binfile|-]
|
||||||
|
%prog -r hexfile
|
||||||
|
%prog --test [logfile]''', version=__version__)
|
||||||
|
parser.add_option('-r', '--restore', action='store_true',
|
||||||
|
help='restore binary from hex dump')
|
||||||
|
parser.add_option('--test', action='store_true', help='run hexdump sanity checks')
|
||||||
|
|
||||||
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
|
if options.test:
|
||||||
|
if args:
|
||||||
|
runtest(logfile=args[0])
|
||||||
|
else:
|
||||||
|
runtest()
|
||||||
|
elif not args or len(args) > 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(-1)
|
||||||
|
else:
|
||||||
|
## dump file
|
||||||
|
if not options.restore:
|
||||||
|
# [x] memory effective dump
|
||||||
|
if args[0] == '-':
|
||||||
|
if not PY3K:
|
||||||
|
hexdump(sys.stdin)
|
||||||
|
else:
|
||||||
|
hexdump(sys.stdin.buffer)
|
||||||
|
else:
|
||||||
|
hexdump(open(args[0], 'rb'))
|
||||||
|
|
||||||
|
## restore file
|
||||||
|
else:
|
||||||
|
# prepare input stream
|
||||||
|
if args[0] == '-':
|
||||||
|
instream = sys.stdin
|
||||||
|
else:
|
||||||
|
if PY3K:
|
||||||
|
instream = open(args[0])
|
||||||
|
else:
|
||||||
|
instream = open(args[0], 'rb')
|
||||||
|
|
||||||
|
# output stream
|
||||||
|
# [ ] memory efficient restore
|
||||||
|
if PY3K:
|
||||||
|
sys.stdout.buffer.write(restore(instream.read()))
|
||||||
|
else:
|
||||||
|
# Windows - binary mode for sys.stdout to prevent data corruption
|
||||||
|
normalize_py()
|
||||||
|
sys.stdout.write(restore(instream.read()))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
|
# [x] file restore from command line utility
|
||||||
|
# [ ] write dump with LF on Windows for consistency
|
||||||
|
# [ ] encoding param for hexdump()ing Python 3 str if anybody requests that
|
||||||
|
|
||||||
|
# [ ] document chunking API
|
||||||
|
# [ ] document hexdump API
|
||||||
|
# [ ] blog about sys.stdout text mode problem on Windows
|
||||||
374
r0capture/r0capture.py
Normal file
374
r0capture/r0capture.py
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
# Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Decrypts and logs a process's SSL traffic.
|
||||||
|
Hooks the functions SSL_read() and SSL_write() in a given process and logs the
|
||||||
|
decrypted data to the console and/or to a pcap file.
|
||||||
|
Typical usage example:
|
||||||
|
ssl_log("wget", "log.pcap", True)
|
||||||
|
Dependencies:
|
||||||
|
frida (https://www.frida.re/):
|
||||||
|
sudo pip install frida
|
||||||
|
hexdump (https://bitbucket.org/techtonik/hexdump/) if using verbose output:
|
||||||
|
sudo pip install hexdump
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "geffner@google.com (Jason Geffner)"
|
||||||
|
__version__ = "2.0"
|
||||||
|
|
||||||
|
"""
|
||||||
|
# r0capture
|
||||||
|
|
||||||
|
ID: r0ysue
|
||||||
|
|
||||||
|
安卓应用层抓包通杀脚本
|
||||||
|
|
||||||
|
https://github.com/r0ysue/r0capture
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
- 仅限安卓平台,测试安卓7、8、9、10 可用 ;
|
||||||
|
- 无视所有证书校验或绑定,无视任何证书;
|
||||||
|
- 通杀TCP/IP四层模型中的应用层中的全部协议;
|
||||||
|
- 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
|
||||||
|
- 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Windows版本需要安装库:
|
||||||
|
# pip install 'win_inet_pton'
|
||||||
|
# pip install hexdump
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
import random
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import frida
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.name == 'nt':
|
||||||
|
import win_inet_pton
|
||||||
|
except ImportError:
|
||||||
|
# win_inet_pton import error
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import myhexdump as hexdump # pylint: disable=g-import-not-at-top
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
from shutil import get_terminal_size as get_terminal_size
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
from backports.shutil_get_terminal_size import get_terminal_size as get_terminal_size
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import click
|
||||||
|
except:
|
||||||
|
class click:
|
||||||
|
@staticmethod
|
||||||
|
def secho(message=None, **kwargs):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def style(**kwargs):
|
||||||
|
raise Exception("unsupported style")
|
||||||
|
banner = """
|
||||||
|
--------------------------------------------------------------------------------------------
|
||||||
|
.oooo. .
|
||||||
|
d8P'`Y8b .o8
|
||||||
|
oooo d8b 888 888 .ooooo. .oooo. oo.ooooo. .o888oo oooo oooo oooo d8b .ooooo.
|
||||||
|
`888""8P 888 888 d88' `"Y8 `P )88b 888' `88b 888 `888 `888 `888""8P d88' `88b
|
||||||
|
888 888 888 888 .oP"888 888 888 888 888 888 888 888ooo888
|
||||||
|
888 `88b d88' 888 .o8 d8( 888 888 888 888 . 888 888 888 888 .o
|
||||||
|
d888b `Y8bd8P' `Y8bod8P' `Y888""8o 888bod8P' "888" `V88V"V8P' d888b `Y8bod8P'
|
||||||
|
888
|
||||||
|
o888o
|
||||||
|
https://github.com/r0ysue/r0capture
|
||||||
|
--------------------------------------------------------------------------------------------\n
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def show_banner():
|
||||||
|
colors = ['bright_red', 'bright_green', 'bright_blue', 'cyan', 'magenta']
|
||||||
|
try:
|
||||||
|
click.style('color test', fg='bright_red')
|
||||||
|
except:
|
||||||
|
colors = ['red', 'green', 'blue', 'cyan', 'magenta']
|
||||||
|
try:
|
||||||
|
columns = get_terminal_size().columns
|
||||||
|
if columns >= len(banner.splitlines()[1]):
|
||||||
|
for line in banner.splitlines():
|
||||||
|
click.secho(line, fg=random.choice(colors))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ssl_session[<SSL_SESSION id>] = (<bytes sent by client>,
|
||||||
|
# <bytes sent by server>)
|
||||||
|
ssl_sessions = {}
|
||||||
|
|
||||||
|
|
||||||
|
def ssl_log(process, pcap=None, host=False, verbose=False, isUsb=False, ssllib="", isSpawn=True, wait=0):
|
||||||
|
"""Decrypts and logs a process's SSL traffic.
|
||||||
|
Hooks the functions SSL_read() and SSL_write() in a given process and logs
|
||||||
|
the decrypted data to the console and/or to a pcap file.
|
||||||
|
Args:
|
||||||
|
process: The target process's name (as a string) or process ID (as an int).
|
||||||
|
pcap: The file path to which the pcap file should be written.
|
||||||
|
verbose: If True, log the decrypted traffic to the console.
|
||||||
|
Raises:
|
||||||
|
NotImplementedError: Not running on a Linux or macOS system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# if platform.system() not in ("Darwin", "Linux"):
|
||||||
|
# raise NotImplementedError("This function is only implemented for Linux and "
|
||||||
|
# "macOS systems.")
|
||||||
|
|
||||||
|
def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
|
||||||
|
dst_addr, dst_port, data):
|
||||||
|
"""Writes the captured data to a pcap file.
|
||||||
|
Args:
|
||||||
|
pcap_file: The opened pcap file.
|
||||||
|
ssl_session_id: The SSL session ID for the communication.
|
||||||
|
function: The function that was intercepted ("SSL_read" or "SSL_write").
|
||||||
|
src_addr: The source address of the logged packet.
|
||||||
|
src_port: The source port of the logged packet.
|
||||||
|
dst_addr: The destination address of the logged packet.
|
||||||
|
dst_port: The destination port of the logged packet.
|
||||||
|
data: The decrypted packet data.
|
||||||
|
"""
|
||||||
|
t = time.time()
|
||||||
|
|
||||||
|
if ssl_session_id not in ssl_sessions:
|
||||||
|
ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),
|
||||||
|
random.randint(0, 0xFFFFFFFF))
|
||||||
|
client_sent, server_sent = ssl_sessions[ssl_session_id]
|
||||||
|
|
||||||
|
if function == "SSL_read":
|
||||||
|
seq, ack = (server_sent, client_sent)
|
||||||
|
else:
|
||||||
|
seq, ack = (client_sent, server_sent)
|
||||||
|
|
||||||
|
for writes in (
|
||||||
|
# PCAP record (packet) header
|
||||||
|
("=I", int(t)), # Timestamp seconds
|
||||||
|
("=I", int((t * 1000000) % 1000000)), # Timestamp microseconds
|
||||||
|
("=I", 40 + len(data)), # Number of octets saved
|
||||||
|
("=i", 40 + len(data)), # Actual length of packet
|
||||||
|
# IPv4 header
|
||||||
|
(">B", 0x45), # Version and Header Length
|
||||||
|
(">B", 0), # Type of Service
|
||||||
|
(">H", 40 + len(data)), # Total Length
|
||||||
|
(">H", 0), # Identification
|
||||||
|
(">H", 0x4000), # Flags and Fragment Offset
|
||||||
|
(">B", 0xFF), # Time to Live
|
||||||
|
(">B", 6), # Protocol
|
||||||
|
(">H", 0), # Header Checksum
|
||||||
|
(">I", src_addr), # Source Address
|
||||||
|
(">I", dst_addr), # Destination Address
|
||||||
|
# TCP header
|
||||||
|
(">H", src_port), # Source Port
|
||||||
|
(">H", dst_port), # Destination Port
|
||||||
|
(">I", seq), # Sequence Number
|
||||||
|
(">I", ack), # Acknowledgment Number
|
||||||
|
(">H", 0x5018), # Header Length and Flags
|
||||||
|
(">H", 0xFFFF), # Window Size
|
||||||
|
(">H", 0), # Checksum
|
||||||
|
(">H", 0)): # Urgent Pointer
|
||||||
|
pcap_file.write(struct.pack(writes[0], writes[1]))
|
||||||
|
pcap_file.write(data)
|
||||||
|
|
||||||
|
if function == "SSL_read":
|
||||||
|
server_sent += len(data)
|
||||||
|
else:
|
||||||
|
client_sent += len(data)
|
||||||
|
ssl_sessions[ssl_session_id] = (client_sent, server_sent)
|
||||||
|
|
||||||
|
def on_message(message, data):
|
||||||
|
"""Callback for errors and messages sent from Frida-injected JavaScript.
|
||||||
|
Logs captured packet data received from JavaScript to the console and/or a
|
||||||
|
pcap file. See https://www.frida.re/docs/messages/ for more detail on
|
||||||
|
Frida's messages.
|
||||||
|
Args:
|
||||||
|
message: A dictionary containing the message "type" and other fields
|
||||||
|
dependent on message type.
|
||||||
|
data: The string of captured decrypted data.
|
||||||
|
"""
|
||||||
|
if message["type"] == "error":
|
||||||
|
logger.info(f"{message}")
|
||||||
|
os.kill(os.getpid(), signal.SIGTERM)
|
||||||
|
return
|
||||||
|
if len(data) == 1:
|
||||||
|
logger.info(f'{message["payload"]["function"]}')
|
||||||
|
logger.info(f'{message["payload"]["stack"]}')
|
||||||
|
return
|
||||||
|
p = message["payload"]
|
||||||
|
if verbose:
|
||||||
|
src_addr = socket.inet_ntop(socket.AF_INET,
|
||||||
|
struct.pack(">I", p["src_addr"]))
|
||||||
|
dst_addr = socket.inet_ntop(socket.AF_INET,
|
||||||
|
struct.pack(">I", p["dst_addr"]))
|
||||||
|
session_id = p['ssl_session_id']
|
||||||
|
logger.info(f"SSL Session: {session_id}")
|
||||||
|
logger.info("[%s] %s:%d --> %s:%d" % (
|
||||||
|
p["function"],
|
||||||
|
src_addr,
|
||||||
|
p["src_port"],
|
||||||
|
dst_addr,
|
||||||
|
p["dst_port"]))
|
||||||
|
gen = hexdump.hexdump(data, result="generator",only_str=True)
|
||||||
|
str_gen = ''.join(gen)
|
||||||
|
logger.info(f"{str_gen}")
|
||||||
|
logger.info(f"{p['stack']}")
|
||||||
|
if pcap:
|
||||||
|
log_pcap(pcap_file, p["ssl_session_id"], p["function"], p["src_addr"],
|
||||||
|
p["src_port"], p["dst_addr"], p["dst_port"], data)
|
||||||
|
|
||||||
|
if isUsb:
|
||||||
|
try:
|
||||||
|
device = frida.get_usb_device()
|
||||||
|
except:
|
||||||
|
device = frida.get_remote_device()
|
||||||
|
else:
|
||||||
|
if host:
|
||||||
|
manager = frida.get_device_manager()
|
||||||
|
device = manager.add_remote_device(host)
|
||||||
|
else:
|
||||||
|
device = frida.get_local_device()
|
||||||
|
|
||||||
|
if isSpawn:
|
||||||
|
pid = device.spawn([process])
|
||||||
|
time.sleep(1)
|
||||||
|
session = device.attach(pid)
|
||||||
|
time.sleep(1)
|
||||||
|
device.resume(pid)
|
||||||
|
else:
|
||||||
|
print("attach")
|
||||||
|
session = device.attach(process)
|
||||||
|
if wait > 0:
|
||||||
|
print(f"wait for {wait} seconds")
|
||||||
|
time.sleep(wait)
|
||||||
|
|
||||||
|
# session = frida.attach(process)
|
||||||
|
|
||||||
|
# pid = device.spawn([process])
|
||||||
|
# pid = process
|
||||||
|
# session = device.attach(pid)
|
||||||
|
# device.resume(pid)
|
||||||
|
if pcap:
|
||||||
|
pcap_file = open(pcap, "wb", 0)
|
||||||
|
for writes in (
|
||||||
|
("=I", 0xa1b2c3d4), # Magic number
|
||||||
|
("=H", 2), # Major version number
|
||||||
|
("=H", 4), # Minor version number
|
||||||
|
("=i", time.timezone), # GMT to local correction
|
||||||
|
("=I", 0), # Accuracy of timestamps
|
||||||
|
("=I", 65535), # Max length of captured packets
|
||||||
|
("=I", 228)): # Data link type (LINKTYPE_IPV4)
|
||||||
|
pcap_file.write(struct.pack(writes[0], writes[1]))
|
||||||
|
|
||||||
|
with open(Path(__file__).resolve().parent.joinpath("./script.js"), encoding="utf-8") as f:
|
||||||
|
_FRIDA_SCRIPT = f.read()
|
||||||
|
# _FRIDA_SCRIPT = session.create_script(content)
|
||||||
|
# print(_FRIDA_SCRIPT)
|
||||||
|
script = session.create_script(_FRIDA_SCRIPT)
|
||||||
|
script.on("message", on_message)
|
||||||
|
script.load()
|
||||||
|
|
||||||
|
if ssllib != "":
|
||||||
|
script.exports.setssllib(ssllib)
|
||||||
|
|
||||||
|
print("Press Ctrl+C to stop logging.")
|
||||||
|
|
||||||
|
def stoplog(signum, frame):
|
||||||
|
print('You have stoped logging.')
|
||||||
|
session.detach()
|
||||||
|
if pcap:
|
||||||
|
pcap_file.flush()
|
||||||
|
pcap_file.close()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, stoplog)
|
||||||
|
signal.signal(signal.SIGTERM, stoplog)
|
||||||
|
sys.stdin.read()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
show_banner()
|
||||||
|
|
||||||
|
|
||||||
|
class ArgParser(argparse.ArgumentParser):
|
||||||
|
|
||||||
|
def error(self, message):
|
||||||
|
print("ssl_logger v" + __version__)
|
||||||
|
print("by " + __author__)
|
||||||
|
print("Modified by BigFaceCat")
|
||||||
|
print("Error: " + message)
|
||||||
|
print()
|
||||||
|
print(self.format_help().replace("usage:", "Usage:"))
|
||||||
|
self.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
parser = ArgParser(
|
||||||
|
add_help=False,
|
||||||
|
description="Decrypts and logs a process's SSL traffic.",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=r"""
|
||||||
|
Examples:
|
||||||
|
%(prog)s -pcap ssl.pcap openssl
|
||||||
|
%(prog)s -verbose 31337
|
||||||
|
%(prog)s -pcap log.pcap -verbose wget
|
||||||
|
%(prog)s -pcap log.pcap -ssl "*libssl.so*" com.bigfacecat.testdemo
|
||||||
|
""")
|
||||||
|
|
||||||
|
args = parser.add_argument_group("Arguments")
|
||||||
|
args.add_argument("-pcap", '-p', metavar="<path>", required=False,
|
||||||
|
help="Name of PCAP file to write")
|
||||||
|
args.add_argument("-host", '-H', metavar="<192.168.1.1:27042>", required=False,
|
||||||
|
help="connect to remote frida-server on HOST")
|
||||||
|
args.add_argument("-verbose", "-v", required=False, action="store_const", default=True,
|
||||||
|
const=True, help="Show verbose output")
|
||||||
|
args.add_argument("process", metavar="<process name | process id>",
|
||||||
|
help="Process whose SSL calls to log")
|
||||||
|
args.add_argument("-ssl", default="", metavar="<lib>",
|
||||||
|
help="SSL library to hook")
|
||||||
|
args.add_argument("--isUsb", "-U", default=False, action="store_true",
|
||||||
|
help="connect to USB device")
|
||||||
|
args.add_argument("--isSpawn", "-f", default=False, action="store_true",
|
||||||
|
help="if spawned app")
|
||||||
|
args.add_argument("-wait", "-w", type=int, metavar="<seconds>", default=0,
|
||||||
|
help="Time to wait for the process")
|
||||||
|
|
||||||
|
parsed = parser.parse_args()
|
||||||
|
logger.add(f"{parsed.process.replace('.','_')}-{int(time.time())}.log", rotation="500MB", encoding="utf-8", enqueue=True, retention="10 days")
|
||||||
|
|
||||||
|
ssl_log(
|
||||||
|
int(parsed.process) if parsed.process.isdigit() else parsed.process,
|
||||||
|
parsed.pcap,
|
||||||
|
parsed.host,
|
||||||
|
parsed.verbose,
|
||||||
|
isUsb=parsed.isUsb,
|
||||||
|
isSpawn=parsed.isSpawn,
|
||||||
|
ssllib=parsed.ssl,
|
||||||
|
wait=parsed.wait
|
||||||
|
)
|
||||||
339
r0capture/script.js
Normal file
339
r0capture/script.js
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
/**
|
||||||
|
* Initializes 'addresses' dictionary and NativeFunctions.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
rpc.exports = {
|
||||||
|
setssllib: function (name) {
|
||||||
|
console.log("setSSLLib => " + name);
|
||||||
|
libname = name;
|
||||||
|
initializeGlobals();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var addresses = {};
|
||||||
|
var SSL_get_fd = null;
|
||||||
|
var SSL_get_session = null;
|
||||||
|
var SSL_SESSION_get_id = null;
|
||||||
|
var getpeername = null;
|
||||||
|
var getsockname = null;
|
||||||
|
var ntohs = null;
|
||||||
|
var ntohl = null;
|
||||||
|
var SSLstackwrite = null;
|
||||||
|
var SSLstackread = null;
|
||||||
|
|
||||||
|
var libname = "*libssl*";
|
||||||
|
|
||||||
|
function uuid(len, radix) {
|
||||||
|
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
|
||||||
|
var uuid = [], i;
|
||||||
|
radix = radix || chars.length;
|
||||||
|
|
||||||
|
if (len) {
|
||||||
|
// Compact form
|
||||||
|
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
|
||||||
|
} else {
|
||||||
|
// rfc4122, version 4 form
|
||||||
|
var r;
|
||||||
|
|
||||||
|
// rfc4122 requires these characters
|
||||||
|
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
||||||
|
uuid[14] = '4';
|
||||||
|
|
||||||
|
// Fill in random data. At i==19 set the high bits of clock sequence as
|
||||||
|
// per rfc4122, sec. 4.1.5
|
||||||
|
for (i = 0; i < 36; i++) {
|
||||||
|
if (!uuid[i]) {
|
||||||
|
r = 0 | Math.random() * 16;
|
||||||
|
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid.join('');
|
||||||
|
}
|
||||||
|
function return_zero(args) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
function initializeGlobals() {
|
||||||
|
var resolver = new ApiResolver("module");
|
||||||
|
var exps = [
|
||||||
|
[Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android
|
||||||
|
[Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]]
|
||||||
|
];
|
||||||
|
// console.log(exps)
|
||||||
|
for (var i = 0; i < exps.length; i++) {
|
||||||
|
var lib = exps[i][0];
|
||||||
|
var names = exps[i][1];
|
||||||
|
for (var j = 0; j < names.length; j++) {
|
||||||
|
var name = names[j];
|
||||||
|
// console.log("exports:" + lib + "!" + name)
|
||||||
|
var matches = resolver.enumerateMatchesSync("exports:" + lib + "!" + name);
|
||||||
|
if (matches.length == 0) {
|
||||||
|
if (name == "SSL_get_fd") {
|
||||||
|
addresses["SSL_get_fd"] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw "Could not find " + lib + "!" + name;
|
||||||
|
}
|
||||||
|
else if (matches.length != 1) {
|
||||||
|
// Sometimes Frida returns duplicates.
|
||||||
|
var address = 0;
|
||||||
|
var s = "";
|
||||||
|
var duplicates_only = true;
|
||||||
|
for (var k = 0; k < matches.length; k++) {
|
||||||
|
if (s.length != 0) {
|
||||||
|
s += ", ";
|
||||||
|
}
|
||||||
|
s += matches[k].name + "@" + matches[k].address;
|
||||||
|
if (address == 0) {
|
||||||
|
address = matches[k].address;
|
||||||
|
}
|
||||||
|
else if (!address.equals(matches[k].address)) {
|
||||||
|
duplicates_only = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!duplicates_only) {
|
||||||
|
throw "More than one match found for " + lib + "!" + name + ": " + s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addresses[name] = matches[0].address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (addresses["SSL_get_fd"] == 0) {
|
||||||
|
SSL_get_fd = return_zero;
|
||||||
|
} else {
|
||||||
|
SSL_get_fd = new NativeFunction(addresses["SSL_get_fd"], "int", ["pointer"]);
|
||||||
|
}
|
||||||
|
SSL_get_session = new NativeFunction(addresses["SSL_get_session"], "pointer", ["pointer"]);
|
||||||
|
SSL_SESSION_get_id = new NativeFunction(addresses["SSL_SESSION_get_id"], "pointer", ["pointer", "pointer"]);
|
||||||
|
getpeername = new NativeFunction(addresses["getpeername"], "int", ["int", "pointer", "pointer"]);
|
||||||
|
getsockname = new NativeFunction(addresses["getsockname"], "int", ["int", "pointer", "pointer"]);
|
||||||
|
ntohs = new NativeFunction(addresses["ntohs"], "uint16", ["uint16"]);
|
||||||
|
ntohl = new NativeFunction(addresses["ntohl"], "uint32", ["uint32"]);
|
||||||
|
}
|
||||||
|
initializeGlobals();
|
||||||
|
|
||||||
|
function ipToNumber(ip) {
|
||||||
|
var num = 0;
|
||||||
|
if (ip == "") {
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
var aNum = ip.split(".");
|
||||||
|
if (aNum.length != 4) {
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
num += parseInt(aNum[0]) << 0;
|
||||||
|
num += parseInt(aNum[1]) << 8;
|
||||||
|
num += parseInt(aNum[2]) << 16;
|
||||||
|
num += parseInt(aNum[3]) << 24;
|
||||||
|
num = num >>> 0;//这个很关键,不然可能会出现负数的情况
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a dictionary of a sockfd's "src_addr", "src_port", "dst_addr", and
|
||||||
|
* "dst_port".
|
||||||
|
* @param {int} sockfd The file descriptor of the socket to inspect.
|
||||||
|
* @param {boolean} isRead If true, the context is an SSL_read call. If
|
||||||
|
* false, the context is an SSL_write call.
|
||||||
|
* @return {dict} Dictionary of sockfd's "src_addr", "src_port", "dst_addr",
|
||||||
|
* and "dst_port".
|
||||||
|
*/
|
||||||
|
function getPortsAndAddresses(sockfd, isRead) {
|
||||||
|
var message = {};
|
||||||
|
var src_dst = ["src", "dst"];
|
||||||
|
for (var i = 0; i < src_dst.length; i++) {
|
||||||
|
if ((src_dst[i] == "src") ^ isRead) {
|
||||||
|
var sockAddr = Socket.localAddress(sockfd)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var sockAddr = Socket.peerAddress(sockfd)
|
||||||
|
}
|
||||||
|
if (sockAddr == null) {
|
||||||
|
// 网络超时or其他原因可能导致socket被关闭
|
||||||
|
message[src_dst[i] + "_port"] = 0
|
||||||
|
message[src_dst[i] + "_addr"] = 0
|
||||||
|
} else {
|
||||||
|
message[src_dst[i] + "_port"] = (sockAddr.port & 0xFFFF)
|
||||||
|
message[src_dst[i] + "_addr"] = ntohl(ipToNumber(sockAddr.ip.split(":").pop()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the session_id of SSL object and return it as a hex string.
|
||||||
|
* @param {!NativePointer} ssl A pointer to an SSL object.
|
||||||
|
* @return {dict} A string representing the session_id of the SSL object's
|
||||||
|
* SSL_SESSION. For example,
|
||||||
|
* "59FD71B7B90202F359D89E66AE4E61247954E28431F6C6AC46625D472FF76336".
|
||||||
|
*/
|
||||||
|
function getSslSessionId(ssl) {
|
||||||
|
var session = SSL_get_session(ssl);
|
||||||
|
if (session == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var len = Memory.alloc(4);
|
||||||
|
var p = SSL_SESSION_get_id(session, len);
|
||||||
|
len = Memory.readU32(len);
|
||||||
|
var session_id = "";
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
// Read a byte, convert it to a hex string (0xAB ==> "AB"), and append
|
||||||
|
// it to session_id.
|
||||||
|
session_id +=
|
||||||
|
("0" + Memory.readU8(p.add(i)).toString(16).toUpperCase()).substr(-2);
|
||||||
|
}
|
||||||
|
return session_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Interceptor.attach(addresses["SSL_read"],
|
||||||
|
{
|
||||||
|
onEnter: function (args) {
|
||||||
|
var message = getPortsAndAddresses(SSL_get_fd(args[0]), true);
|
||||||
|
message["ssl_session_id"] = getSslSessionId(args[0]);
|
||||||
|
message["function"] = "SSL_read";
|
||||||
|
message["stack"] = SSLstackread;
|
||||||
|
this.message = message;
|
||||||
|
this.buf = args[1];
|
||||||
|
},
|
||||||
|
onLeave: function (retval) {
|
||||||
|
retval |= 0; // Cast retval to 32-bit integer.
|
||||||
|
if (retval <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send(this.message, Memory.readByteArray(this.buf, retval));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Interceptor.attach(addresses["SSL_write"],
|
||||||
|
{
|
||||||
|
onEnter: function (args) {
|
||||||
|
var message = getPortsAndAddresses(SSL_get_fd(args[0]), false);
|
||||||
|
message["ssl_session_id"] = getSslSessionId(args[0]);
|
||||||
|
message["function"] = "SSL_write";
|
||||||
|
message["stack"] = SSLstackwrite;
|
||||||
|
send(message, Memory.readByteArray(args[1], parseInt(args[2])));
|
||||||
|
},
|
||||||
|
onLeave: function (retval) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Java.available) {
|
||||||
|
Java.perform(function () {
|
||||||
|
function storeP12(pri, p7, p12Path, p12Password) {
|
||||||
|
var X509Certificate = Java.use("java.security.cert.X509Certificate")
|
||||||
|
var p7X509 = Java.cast(p7, X509Certificate);
|
||||||
|
var chain = Java.array("java.security.cert.X509Certificate", [p7X509])
|
||||||
|
var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC");
|
||||||
|
ks.load(null, null);
|
||||||
|
ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);
|
||||||
|
try {
|
||||||
|
var out = Java.use("java.io.FileOutputStream").$new(p12Path);
|
||||||
|
ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())
|
||||||
|
} catch (exp) {
|
||||||
|
console.log(exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
|
||||||
|
Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () {
|
||||||
|
var result = this.getPrivateKey()
|
||||||
|
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
|
||||||
|
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
|
||||||
|
var message = {};
|
||||||
|
message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue';
|
||||||
|
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
|
||||||
|
var data = Memory.alloc(1);
|
||||||
|
send(message, Memory.readByteArray(data, 1))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () {
|
||||||
|
var result = this.getCertificateChain()
|
||||||
|
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
|
||||||
|
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
|
||||||
|
var message = {};
|
||||||
|
message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue';
|
||||||
|
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
|
||||||
|
var data = Memory.alloc(1);
|
||||||
|
send(message, Memory.readByteArray(data, 1))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//SSLpinning helper 帮助定位证书绑定的关键代码a
|
||||||
|
Java.use("java.io.File").$init.overload('java.io.File', 'java.lang.String').implementation = function (file, cert) {
|
||||||
|
var result = this.$init(file, cert)
|
||||||
|
var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
|
||||||
|
if (file.getPath().indexOf("cacert") >= 0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted") >= 0) {
|
||||||
|
var message = {};
|
||||||
|
message["function"] = "SSLpinning position locator => " + file.getPath() + " " + cert;
|
||||||
|
message["stack"] = stack;
|
||||||
|
var data = Memory.alloc(1);
|
||||||
|
send(message, Memory.readByteArray(data, 1))
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {
|
||||||
|
var result = this.socketWrite0(fd, bytearry, offset, byteCount);
|
||||||
|
var message = {};
|
||||||
|
message["function"] = "HTTP_send";
|
||||||
|
message["ssl_session_id"] = "";
|
||||||
|
message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
|
||||||
|
message["src_port"] = parseInt(this.socket.value.getLocalPort().toString());
|
||||||
|
message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
|
||||||
|
message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
|
||||||
|
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
|
||||||
|
var ptr = Memory.alloc(byteCount);
|
||||||
|
for (var i = 0; i < byteCount; ++i)
|
||||||
|
Memory.writeS8(ptr.add(i), bytearry[offset + i]);
|
||||||
|
send(message, Memory.readByteArray(ptr, byteCount))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {
|
||||||
|
var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
|
||||||
|
var message = {};
|
||||||
|
message["function"] = "HTTP_recv";
|
||||||
|
message["ssl_session_id"] = "";
|
||||||
|
message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
|
||||||
|
message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
|
||||||
|
message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
|
||||||
|
message["dst_port"] = parseInt(this.socket.value.getLocalPort());
|
||||||
|
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
|
||||||
|
if (result > 0) {
|
||||||
|
var ptr = Memory.alloc(result);
|
||||||
|
for (var i = 0; i < result; ++i)
|
||||||
|
Memory.writeS8(ptr.add(i), bytearry[offset + i]);
|
||||||
|
send(message, Memory.readByteArray(ptr, result))
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseFloat(Java.androidVersion) > 8) {
|
||||||
|
Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
|
||||||
|
var result = this.write(bytearry, int1, int2);
|
||||||
|
SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
|
||||||
|
var result = this.read(bytearry, int1, int2);
|
||||||
|
SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
|
||||||
|
var result = this.write(bytearry, int1, int2);
|
||||||
|
SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
|
||||||
|
var result = this.read(bytearry, int1, int2);
|
||||||
|
SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
colorama==0.4.6
|
||||||
|
frida==16.0.19
|
||||||
|
frida-tools==12.2.1
|
||||||
|
loguru==0.7.2
|
||||||
|
prompt-toolkit==3.0.38
|
||||||
|
Pygments==2.15.1
|
||||||
|
typing_extensions==4.6.3
|
||||||
|
wcwidth==0.2.6
|
||||||
21
scripts/baidunetdisk.js
Normal file
21
scripts/baidunetdisk.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
console.log("Script loaded successfully");
|
||||||
|
|
||||||
|
Java.perform(function () {
|
||||||
|
var Request = Java.use("okhttp3.Request");
|
||||||
|
|
||||||
|
// Hook Request的toString方法
|
||||||
|
Request.toString.implementation = function () {
|
||||||
|
// 调用原始的toString方法并保存结果
|
||||||
|
var result = this.toString();
|
||||||
|
|
||||||
|
// 打印结果
|
||||||
|
console.log("Request.toString result: " + result);
|
||||||
|
|
||||||
|
// 返回原始方法调用的结果
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
197
scripts/hook_conversions.js
Normal file
197
scripts/hook_conversions.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
log_info("Script loaded successfully");
|
||||||
|
// hook_okhttp_client()
|
||||||
|
if (Java.available) {
|
||||||
|
hook_json()
|
||||||
|
let class_name = "okhttp3.OkHttpClient"
|
||||||
|
if (check_class(class_name)){
|
||||||
|
hook_okhttp_client()
|
||||||
|
}
|
||||||
|
Java.perform(function () {
|
||||||
|
log_info("start hook java.net.URL");
|
||||||
|
var URL = Java.use('java.net.URL');
|
||||||
|
URL.$init.overload('java.lang.String').implementation = function (spec) {
|
||||||
|
// console.log("URL request:" + spec)
|
||||||
|
log_info("URL request: " + spec)
|
||||||
|
if (spec.includes("appsflyer")) {
|
||||||
|
// console.log("URL request: " + spec);
|
||||||
|
if (spec.includes("conversions")) {
|
||||||
|
var stackTrace = Java.use('java.lang.Exception').$new().getStackTrace().toString();
|
||||||
|
// console.log(stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.$init(spec);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function printMethods(className) {
|
||||||
|
log_info("start print methods.")
|
||||||
|
var jclass = Java.use(className);
|
||||||
|
var methods = jclass.class.getDeclaredMethods();
|
||||||
|
console.log("Printing methods of " + className + ":\n");
|
||||||
|
methods.forEach(function (method) {
|
||||||
|
// console.log(method);
|
||||||
|
log_info("The methods under the class" + className + " are: " + method);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hook_okhttp_client() {
|
||||||
|
if (Java.available) {
|
||||||
|
Java.perform(function () {
|
||||||
|
log_info("start hook_okhttp_client.")
|
||||||
|
try {
|
||||||
|
var OkHttpClient = Java.use("okhttp3.OkHttpClient");
|
||||||
|
OkHttpClient.newCall.overload('okhttp3.Request').implementation = function (request) {
|
||||||
|
var requestUrl = request.url();
|
||||||
|
if (requestUrl) {
|
||||||
|
console.log("OkHttp Request URL: " + requestUrl.toString());
|
||||||
|
} else {
|
||||||
|
console.log("OkHttp Request URL is not available");
|
||||||
|
}
|
||||||
|
console.log("OkHttp Request Headers: " + request.headers().toString());
|
||||||
|
|
||||||
|
if (request.method() == "POST") {
|
||||||
|
console.log("OkHttp Request Body: " + request.body().contentType().toString());
|
||||||
|
}
|
||||||
|
var call = this.newCall(request);
|
||||||
|
var response = call.execute();
|
||||||
|
console.log("OkHttp Response: " + response.body().string());
|
||||||
|
return call;
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error hooking OkHttp: " + e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hook_HttpURLConnection_stream() {
|
||||||
|
console.log("start hook_HttpURLConnection_stream")
|
||||||
|
if (Java.available) {
|
||||||
|
Java.perform(function () {
|
||||||
|
var HttpURLConnection = Java.use("java.net.HttpURLConnection");
|
||||||
|
|
||||||
|
HttpURLConnection.getOutputStream.implementation = function () {
|
||||||
|
var outputStream = this.getOutputStream();
|
||||||
|
var OutputStreamWrapper = Java.use("java.io.OutputStream");
|
||||||
|
|
||||||
|
var newOutputStream = Java.registerClass({
|
||||||
|
name: "CustomOutputStream",
|
||||||
|
superClass: OutputStreamWrapper,
|
||||||
|
methods: {
|
||||||
|
write: function (buffer, byteOffset, byteCount) {
|
||||||
|
var data = Array.prototype.slice.call(buffer.slice(byteOffset, byteOffset + byteCount));
|
||||||
|
console.log("Request data: " + String.fromCharCode.apply(null, data));
|
||||||
|
outputStream.write(buffer, byteOffset, byteCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newOutputStream.$new(outputStream);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpURLConnection.getInputStream.implementation = function () {
|
||||||
|
var inputStream = this.getInputStream();
|
||||||
|
var InputStreamWrapper = Java.use("java.io.InputStream");
|
||||||
|
|
||||||
|
var newInputStream = Java.registerClass({
|
||||||
|
name: "CustomInputStream",
|
||||||
|
superClass: InputStreamWrapper,
|
||||||
|
methods: {
|
||||||
|
read: function (buffer, byteOffset, byteCount) {
|
||||||
|
var bytesRead = inputStream.read(buffer, byteOffset, byteCount);
|
||||||
|
if (bytesRead != -1) {
|
||||||
|
var data = Array.prototype.slice.call(buffer.slice(byteOffset, byteOffset + bytesRead));
|
||||||
|
console.log("Response data: " + String.fromCharCode.apply(null, data));
|
||||||
|
}
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newInputStream.$new(inputStream);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function hook_retrofit() {
|
||||||
|
Java.perform(function () {
|
||||||
|
var retrofitBuilder = Java.use("retrofit2.Retrofit$Builder");
|
||||||
|
retrofitBuilder.build.implementation = function () {
|
||||||
|
var retrofit = this.build();
|
||||||
|
var httpClient = retrofit.callFactory().clone();
|
||||||
|
httpClient.interceptors().add(new Java.use('okhttp3.Interceptor')({
|
||||||
|
intercept: function (chain) {
|
||||||
|
console.log("HTTP Request -> " + chain.request().toString())
|
||||||
|
var response = chain.proceed(chain.request());
|
||||||
|
console.log("HTTP Response -> " + response.toString());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return retrofit.newBuilder()
|
||||||
|
.callFactory(httpClient)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function hook_json() {
|
||||||
|
Java.perform(function () {
|
||||||
|
var JSONObject = Java.use('org.json.JSONObject');
|
||||||
|
JSONObject.toString.overload().implementation = function () {
|
||||||
|
var result = this.toString.call(this);
|
||||||
|
// get_conversions(result)
|
||||||
|
// log_info("Serialized JSONObject: " + result)
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function log_info(messages) {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
|
||||||
|
|
||||||
|
const timestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}:${milliseconds}`;
|
||||||
|
|
||||||
|
console.log(`${timestamp} - ${messages}`);
|
||||||
|
send(`${timestamp} - ${messages}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_class(class_name) {
|
||||||
|
var classFound = false; // 默认为未找到
|
||||||
|
Java.enumerateLoadedClasses({
|
||||||
|
onMatch: function(currentClassName) {
|
||||||
|
if (currentClassName === class_name) {
|
||||||
|
classFound = true; // 如果找到了类,则设置为true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onComplete: function() {
|
||||||
|
if (classFound) {
|
||||||
|
log_info(class_name + " has been loaded!");
|
||||||
|
} else {
|
||||||
|
log_info(class_name + " has not been loaded yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return classFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
recv('input_data', function(payload) {
|
||||||
|
console.log(typeof(payload))
|
||||||
|
|
||||||
|
console.log(JSON.stringify(payload))
|
||||||
|
console.log('Received data from Python: ' + payload.payload.data);
|
||||||
|
});
|
||||||
|
|
||||||
40
scripts/hook_qqmusic.js
Normal file
40
scripts/hook_qqmusic.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
log_info("Script loaded successfully");
|
||||||
|
if (Java.available) {
|
||||||
|
hook_json()
|
||||||
|
Java.perform(function () {
|
||||||
|
log_info("start hook java.net.URL");
|
||||||
|
var URL = Java.use('java.net.URL');
|
||||||
|
URL.$init.overload('java.lang.String').implementation = function (spec) {
|
||||||
|
log_info("URL request: " + spec)
|
||||||
|
return this.$init(spec);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function log_info(messages) {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
|
||||||
|
|
||||||
|
const timestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}:${milliseconds}`;
|
||||||
|
|
||||||
|
console.log(`${timestamp} - ${messages}`);
|
||||||
|
send(`${timestamp} - ${messages}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hook_json() {
|
||||||
|
Java.perform(function () {
|
||||||
|
var JSONObject = Java.use('org.json.JSONObject');
|
||||||
|
JSONObject.toString.overload().implementation = function () {
|
||||||
|
var result = this.toString.call(this);
|
||||||
|
// get_conversions(result)
|
||||||
|
log_info("Serialized JSONObject: " + result)
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
362
scripts/hookssl.js
Normal file
362
scripts/hookssl.js
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
console.log("Script loaded successfully");
|
||||||
|
Java.perform(function() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
hook list:
|
||||||
|
1.SSLcontext
|
||||||
|
2.okhttp
|
||||||
|
3.webview
|
||||||
|
4.XUtils
|
||||||
|
5.httpclientandroidlib
|
||||||
|
6.JSSE
|
||||||
|
7.network\_security\_config (android 7.0+)
|
||||||
|
8.Apache Http client (support partly)
|
||||||
|
9.OpenSSLSocketImpl
|
||||||
|
10.TrustKit
|
||||||
|
11.Cronet
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Attempts to bypass SSL pinning implementations in a number of
|
||||||
|
// ways. These include implementing a new TrustManager that will
|
||||||
|
// accept any SSL certificate, overriding OkHTTP v3 check()
|
||||||
|
// method etc.
|
||||||
|
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
|
||||||
|
var HostnameVerifier = Java.use('javax.net.ssl.HostnameVerifier');
|
||||||
|
var SSLContext = Java.use('javax.net.ssl.SSLContext');
|
||||||
|
var quiet_output = false;
|
||||||
|
|
||||||
|
// Helper method to honor the quiet flag.
|
||||||
|
|
||||||
|
function quiet_send(data) {
|
||||||
|
|
||||||
|
if (quiet_output) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Implement a new TrustManager
|
||||||
|
// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
|
||||||
|
// Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切换成ART使用.
|
||||||
|
/*
|
||||||
|
06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
|
||||||
|
06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err: at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
|
||||||
|
at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
|
||||||
|
06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err: at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
|
||||||
|
at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
|
||||||
|
at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
|
||||||
|
*/
|
||||||
|
var X509Certificate = Java.use("java.security.cert.X509Certificate");
|
||||||
|
var TrustManager;
|
||||||
|
try {
|
||||||
|
TrustManager = Java.registerClass({
|
||||||
|
name: 'org.wooyun.TrustManager',
|
||||||
|
implements: [X509TrustManager],
|
||||||
|
methods: {
|
||||||
|
checkClientTrusted: function(chain, authType) {},
|
||||||
|
checkServerTrusted: function(chain, authType) {},
|
||||||
|
getAcceptedIssuers: function() {
|
||||||
|
// var certs = [X509Certificate.$new()];
|
||||||
|
// return certs;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
quiet_send("registerClass from X509TrustManager >>>>>>>> " + e.message);
|
||||||
|
}
|
||||||
|
// Prepare the TrustManagers array to pass to SSLContext.init()
|
||||||
|
var TrustManagers = [TrustManager.$new()];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prepare a Empty SSLFactory
|
||||||
|
var TLS_SSLContext = SSLContext.getInstance("TLS");
|
||||||
|
TLS_SSLContext.init(null, TrustManagers, null);
|
||||||
|
var EmptySSLFactory = TLS_SSLContext.getSocketFactory();
|
||||||
|
} catch (e) {
|
||||||
|
quiet_send(e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
send('Custom, Empty TrustManager ready');
|
||||||
|
|
||||||
|
// Get a handle on the init() on the SSLContext class
|
||||||
|
var SSLContext_init = SSLContext.init.overload(
|
||||||
|
'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');
|
||||||
|
|
||||||
|
// Override the init method, specifying our new TrustManager
|
||||||
|
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
|
||||||
|
|
||||||
|
quiet_send('Overriding SSLContext.init() with the custom TrustManager');
|
||||||
|
|
||||||
|
SSLContext_init.call(this, null, TrustManagers, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*** okhttp3.x unpinning ***/
|
||||||
|
|
||||||
|
|
||||||
|
// Wrap the logic in a try/catch as not all applications will have
|
||||||
|
// okhttp as part of the app.
|
||||||
|
try {
|
||||||
|
|
||||||
|
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
|
||||||
|
|
||||||
|
quiet_send('OkHTTP 3.x Found');
|
||||||
|
|
||||||
|
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
|
||||||
|
|
||||||
|
quiet_send('OkHTTP 3.x check() called. Not throwing an exception.');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
// If we dont have a ClassNotFoundException exception, raise the
|
||||||
|
// problem encountered.
|
||||||
|
if (err.message.indexOf('ClassNotFoundException') === 0) {
|
||||||
|
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appcelerator Titanium PinningTrustManager
|
||||||
|
|
||||||
|
// Wrap the logic in a try/catch as not all applications will have
|
||||||
|
// appcelerator as part of the app.
|
||||||
|
try {
|
||||||
|
|
||||||
|
var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');
|
||||||
|
|
||||||
|
send('Appcelerator Titanium Found');
|
||||||
|
|
||||||
|
PinningTrustManager.checkServerTrusted.implementation = function() {
|
||||||
|
|
||||||
|
quiet_send('Appcelerator checkServerTrusted() called. Not throwing an exception.');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
// If we dont have a ClassNotFoundException exception, raise the
|
||||||
|
// problem encountered.
|
||||||
|
if (err.message.indexOf('ClassNotFoundException') === 0) {
|
||||||
|
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** okhttp unpinning ***/
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
|
||||||
|
OkHttpClient.setCertificatePinner.implementation = function(certificatePinner) {
|
||||||
|
// do nothing
|
||||||
|
quiet_send("OkHttpClient.setCertificatePinner Called!");
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
|
||||||
|
var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
|
||||||
|
CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(p0, p1) {
|
||||||
|
// do nothing
|
||||||
|
quiet_send("okhttp Called! [Certificate]");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(p0, p1) {
|
||||||
|
// do nothing
|
||||||
|
quiet_send("okhttp Called! [List]");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
quiet_send("com.squareup.okhttp not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** WebView Hooks ***/
|
||||||
|
|
||||||
|
/* frameworks/base/core/java/android/webkit/WebViewClient.java */
|
||||||
|
/* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
|
||||||
|
var WebViewClient = Java.use("android.webkit.WebViewClient");
|
||||||
|
|
||||||
|
WebViewClient.onReceivedSslError.implementation = function(webView, sslErrorHandler, sslError) {
|
||||||
|
quiet_send("WebViewClient onReceivedSslError invoke");
|
||||||
|
//执行proceed方法
|
||||||
|
sslErrorHandler.proceed();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
WebViewClient.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c, d) {
|
||||||
|
quiet_send("WebViewClient onReceivedError invoked");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
WebViewClient.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function() {
|
||||||
|
quiet_send("WebViewClient onReceivedError invoked");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*** JSSE Hooks ***/
|
||||||
|
|
||||||
|
/* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
|
||||||
|
/* public final TrustManager[] getTrustManager() */
|
||||||
|
/* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error */
|
||||||
|
// var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
|
||||||
|
// TrustManagerFactory.getTrustManagers.implementation = function(){
|
||||||
|
// quiet_send("TrustManagerFactory getTrustManagers invoked");
|
||||||
|
// return TrustManagers;
|
||||||
|
// }
|
||||||
|
|
||||||
|
var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection");
|
||||||
|
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
|
||||||
|
/* public void setDefaultHostnameVerifier(HostnameVerifier) */
|
||||||
|
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function(hostnameVerifier) {
|
||||||
|
quiet_send("HttpsURLConnection.setDefaultHostnameVerifier invoked");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
|
||||||
|
/* public void setSSLSocketFactory(SSLSocketFactory) */
|
||||||
|
HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
|
||||||
|
quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
|
||||||
|
/* public void setHostnameVerifier(HostnameVerifier) */
|
||||||
|
HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
|
||||||
|
quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*** Xutils3.x hooks ***/
|
||||||
|
//Implement a new HostnameVerifier
|
||||||
|
var TrustHostnameVerifier;
|
||||||
|
try {
|
||||||
|
TrustHostnameVerifier = Java.registerClass({
|
||||||
|
name: 'org.wooyun.TrustHostnameVerifier',
|
||||||
|
implements: [HostnameVerifier],
|
||||||
|
method: {
|
||||||
|
verify: function(hostname, session) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
//java.lang.ClassNotFoundException: Didn't find class "org.wooyun.TrustHostnameVerifier"
|
||||||
|
quiet_send("registerClass from hostnameVerifier >>>>>>>> " + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var RequestParams = Java.use('org.xutils.http.RequestParams');
|
||||||
|
RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
|
||||||
|
sslSocketFactory = EmptySSLFactory;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
|
||||||
|
hostnameVerifier = TrustHostnameVerifier.$new();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
quiet_send("Xutils hooks not Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** httpclientandroidlib Hooks ***/
|
||||||
|
try {
|
||||||
|
var AbstractVerifier = Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
|
||||||
|
AbstractVerifier.verify.overload('java.lang.String', '[Ljava.lang.String', '[Ljava.lang.String', 'boolean').implementation = function() {
|
||||||
|
quiet_send("httpclientandroidlib Hooks");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
quiet_send("httpclientandroidlib Hooks not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
android 7.0+ network_security_config TrustManagerImpl hook
|
||||||
|
apache httpclient partly
|
||||||
|
***/
|
||||||
|
var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
|
||||||
|
// try {
|
||||||
|
// var Arrays = Java.use("java.util.Arrays");
|
||||||
|
// //apache http client pinning maybe baypass
|
||||||
|
// //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
|
||||||
|
// TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
|
||||||
|
// quiet_send("TrustManagerImpl checkTrusted called");
|
||||||
|
// //Generics currently result in java.lang.Object
|
||||||
|
// return Arrays.asList(chain);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// } catch (e) {
|
||||||
|
// quiet_send("TrustManagerImpl checkTrusted nout found");
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Android 7+ TrustManagerImpl
|
||||||
|
TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
|
||||||
|
quiet_send("TrustManagerImpl verifyChain called");
|
||||||
|
// Skip all the logic and just return the chain again :P
|
||||||
|
//https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
|
||||||
|
// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
|
||||||
|
return untrustedChain;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
quiet_send("TrustManagerImpl verifyChain nout found below 7.0");
|
||||||
|
}
|
||||||
|
// OpenSSLSocketImpl
|
||||||
|
try {
|
||||||
|
var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
|
||||||
|
OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, authMethod) {
|
||||||
|
quiet_send('OpenSSLSocketImpl.verifyCertificateChain');
|
||||||
|
}
|
||||||
|
|
||||||
|
quiet_send('OpenSSLSocketImpl pinning')
|
||||||
|
} catch (err) {
|
||||||
|
quiet_send('OpenSSLSocketImpl pinner not found');
|
||||||
|
}
|
||||||
|
// Trustkit
|
||||||
|
try {
|
||||||
|
var Activity = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
|
||||||
|
Activity.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(str) {
|
||||||
|
quiet_send('Trustkit.verify1: ' + str);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
Activity.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(str) {
|
||||||
|
quiet_send('Trustkit.verify2: ' + str);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
quiet_send('Trustkit pinning')
|
||||||
|
} catch (err) {
|
||||||
|
quiet_send('Trustkit pinner not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//cronet pinner hook
|
||||||
|
//weibo don't invoke
|
||||||
|
|
||||||
|
var netBuilder = Java.use("org.chromium.net.CronetEngine$Builder");
|
||||||
|
|
||||||
|
//https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
|
||||||
|
netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation = function(arg) {
|
||||||
|
|
||||||
|
//weibo not invoke
|
||||||
|
console.log("Enables or disables public key pinning bypass for local trust anchors = " + arg);
|
||||||
|
|
||||||
|
//true to enable the bypass, false to disable.
|
||||||
|
var ret = netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
netBuilder.addPublicKeyPins.implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
|
||||||
|
console.log("cronet addPublicKeyPins hostName = " + hostName);
|
||||||
|
|
||||||
|
//var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
|
||||||
|
//this 是调用 addPublicKeyPins 前的对象吗? Yes,CronetEngine.Builder
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.log('[-] Cronet pinner not found')
|
||||||
|
}
|
||||||
|
});
|
||||||
32
scripts/monitor_request.js
Normal file
32
scripts/monitor_request.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
log_info("Script loaded successfully");
|
||||||
|
Java.perform(function () {
|
||||||
|
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
|
||||||
|
var Request = Java.use('okhttp3.Request');
|
||||||
|
|
||||||
|
OkHttpClient.newCall.implementation = function (request) {
|
||||||
|
var url = request.url().toString();
|
||||||
|
var method = request.method();
|
||||||
|
var body = request.body();
|
||||||
|
var size = body != null ? body.contentLength() / 1024 : 0;
|
||||||
|
console.log("Method: " + method + "\nURL: " + url + "\nSize: " + size + " kb");
|
||||||
|
|
||||||
|
return this.newCall(request);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function log_info(messages) {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
|
||||||
|
|
||||||
|
const timestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}:${milliseconds}`;
|
||||||
|
|
||||||
|
console.log(`${timestamp} - ${messages}`);
|
||||||
|
send(`${timestamp} - ${messages}`);
|
||||||
|
}
|
||||||
20
scripts/test.js
Normal file
20
scripts/test.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Java.perform(function() {
|
||||||
|
var ByteArrayOutputStream = Java.use('java.io.ByteArrayOutputStream');
|
||||||
|
var HttpsURLConnection = Java.use('javax.net.ssl.HttpsURLConnection');
|
||||||
|
|
||||||
|
HttpsURLConnection.getOutputStream.implementation = function() {
|
||||||
|
var outputStream = this.getOutputStream();
|
||||||
|
|
||||||
|
// 创建一个新的 ByteArrayOutputStream 实例,以便于我们读取数据。
|
||||||
|
var byteArrayOutputStream = ByteArrayOutputStream.$new();
|
||||||
|
|
||||||
|
outputStream.write.overload('[B').implementation = function(buffer) {
|
||||||
|
byteArrayOutputStream.write(buffer);
|
||||||
|
console.log("[*] Data written to URL:", this.getURL().toString());
|
||||||
|
console.log("[*] Data:", byteArrayOutputStream.toString('UTF-8'));
|
||||||
|
return outputStream.write(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return outputStream;
|
||||||
|
};
|
||||||
|
});
|
||||||
82
scripts/url_request.js
Normal file
82
scripts/url_request.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
Java.perform(function() {
|
||||||
|
var URL = Java.use("java.net.URL");
|
||||||
|
var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection");
|
||||||
|
var HttpURLConnection = Java.use("java.net.HttpURLConnection");
|
||||||
|
var OutputStreamWriter = Java.use("java.io.OutputStreamWriter");
|
||||||
|
var BufferedReader = Java.use("java.io.BufferedReader");
|
||||||
|
var InputStreamReader = Java.use("java.io.InputStreamReader");
|
||||||
|
var OutputStream = Java.use('java.io.OutputStream');
|
||||||
|
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
|
||||||
|
|
||||||
|
URL.$init.overload('java.lang.String').implementation = function(url) {
|
||||||
|
console.log("[*] URL init:", url.toString());
|
||||||
|
return this.$init(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpsURLConnection.setDoOutput.implementation = function(value) {
|
||||||
|
console.log("[*]HttpsURLConnection setDoOutput:", value);
|
||||||
|
return this.setDoOutput(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpsURLConnection.setRequestProperty.implementation = function(key, value) {
|
||||||
|
console.log("[*] setRequestProperty:", key, value);
|
||||||
|
return this.setRequestProperty(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
HttpsURLConnection.getOutputStream.implementation = function() {
|
||||||
|
console.log("[*] getOutputStream");
|
||||||
|
return this.getOutputStream();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HttpURLConnection.setRequestProperty.implementation = function(key, value) {
|
||||||
|
console.log("[*] setRequestProperty:", key, value);
|
||||||
|
return this.setRequestProperty(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpURLConnection.setDoOutput.implementation = function(value) {
|
||||||
|
console.log("[*]HttpURLConnection setDoOutput:", value);
|
||||||
|
return this.setDoOutput(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// HttpURLConnection.getOutputStream.implementation = function() {
|
||||||
|
// console.log("[*] getOutputStream");
|
||||||
|
// var outputStream = this.getOutputStream();
|
||||||
|
//
|
||||||
|
// outputStream.write.overload('[B').implementation = function(buffer) {
|
||||||
|
// console.log("[*] Data written:", Java.array('byte', buffer).toString());
|
||||||
|
// return this.write(buffer);
|
||||||
|
// };
|
||||||
|
// return outputStream;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// OutputStream.write.overload('[B').implementation = function(buffer) {
|
||||||
|
// console.log("[*] Data written:", Java.array('byte', buffer).toString());
|
||||||
|
// return this.write(buffer);
|
||||||
|
// };
|
||||||
|
|
||||||
|
BufferedReader.readLine.overload().implementation = function() {
|
||||||
|
var line = this.readLine();
|
||||||
|
// console.log("[*] BufferedReader.readLine:", line);
|
||||||
|
return line;
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStreamReader.$init.overload('java.io.InputStream').implementation = function(stream) {
|
||||||
|
console.log("[*] InputStreamReader.init:", stream);
|
||||||
|
return this.$init(stream);
|
||||||
|
};
|
||||||
|
|
||||||
|
OkHttpClient.newCall.overload('okhttp3.Request').implementation = function(request) {
|
||||||
|
console.log("[*] Request URL:", request.url().toString());
|
||||||
|
console.log("[*] Request Headers:", request.headers().toString());
|
||||||
|
|
||||||
|
if (request.method() == "POST") {
|
||||||
|
console.log("[*] Request Body:", request.body().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.newCall(request);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
33
test.py
33
test.py
@@ -1,24 +1,11 @@
|
|||||||
import frida
|
import frida,sys
|
||||||
|
import modules.files_utils
|
||||||
|
|
||||||
|
js_code = modules.files_utils.read_javascript("scripts/hook_conversions.js")
|
||||||
def is_frida_running(device):
|
device = frida.get_usb_device()
|
||||||
try:
|
pid = device.spawn(["com.naviapp"]) # 以挂起方式创建进程
|
||||||
# 获取设备上的所有进程
|
process = device.attach(pid)
|
||||||
processes = device.enumerate_processes()
|
script = process.create_script(js_code)
|
||||||
|
script.load()
|
||||||
# 检查是否存在名为 'frida-server' 的进程
|
device.resume(pid) # 加载完脚本, 恢复进程运行
|
||||||
for process in processes:
|
sys.stdin.read()
|
||||||
print(process)
|
|
||||||
if process.name == 'frida':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
device = frida.get_usb_device(timeout=3)
|
|
||||||
if is_frida_running(device):
|
|
||||||
print("Frida is running on the device.")
|
|
||||||
else:
|
|
||||||
print("Frida is not running on the device.")
|
|
||||||
|
|||||||
Reference in New Issue
Block a user