Compare commits

...

8 Commits

Author SHA1 Message Date
wangsiyuan 19333bc43a 创建 com.vmall.client.pcap 2023-09-25 17:11:32 +08:00
wangsiyuan e74d6554e9 创建 VMALL-1695632341.log 2023-09-25 17:11:29 +08:00
wangsiyuan 528b0d8885 创建 script.js 2023-09-25 17:11:27 +08:00
wangsiyuan 23640b607a 创建 r0capture.py 2023-09-25 17:11:24 +08:00
wangsiyuan 649dd5ac89 创建 myhexdump.py 2023-09-25 17:11:23 +08:00
wangsiyuan 27631b814a 更新 frida抓包.md 2023-09-25 17:09:40 +08:00
wangsiyuan c8b014e457 更新 frida抓包.md 2023-09-25 15:29:07 +08:00
wangsiyuan b46444dea2 创建 frida抓包.md 2023-09-25 15:23:19 +08:00
6 changed files with 2507 additions and 0 deletions

1256
VMALL-1695632341.log Normal file

File diff suppressed because one or more lines are too long

BIN
com.vmall.client.pcap Normal file

Binary file not shown.

59
frida抓包.md Normal file
View File

@ -0,0 +1,59 @@
# 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
```

479
r0capture/myhexdump.py Normal file
View 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
View 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
## 简介
- 仅限安卓平台测试安卓78910 可用
- 无视所有证书校验或绑定无视任何证书
- 通杀TCP/IP四层模型中的应用层中的全部协议
- 通杀协议包括Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等以及它们的SSL版本
- 通杀所有应用层框架包括HttpUrlConnectionOkhttp1/3/4Retrofit/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
View 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;
}
}
}
)
}