Compare commits
8 Commits
8fc63ad9c0
...
19333bc43a
| Author | SHA1 | Date | |
|---|---|---|---|
| 19333bc43a | |||
| e74d6554e9 | |||
| 528b0d8885 | |||
| 23640b607a | |||
| 649dd5ac89 | |||
| 27631b814a | |||
| c8b014e457 | |||
| b46444dea2 |
1256
VMALL-1695632341.log
Normal file
1256
VMALL-1695632341.log
Normal file
File diff suppressed because one or more lines are too long
BIN
com.vmall.client.pcap
Normal file
BIN
com.vmall.client.pcap
Normal file
Binary file not shown.
59
frida抓包.md
Normal file
59
frida抓包.md
Normal 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
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user