Compare commits
8 Commits
8fc63ad9c0
...
19333bc43a
| Author | SHA1 | Date |
|---|---|---|
|
|
19333bc43a | |
|
|
e74d6554e9 | |
|
|
528b0d8885 | |
|
|
23640b607a | |
|
|
649dd5ac89 | |
|
|
27631b814a | |
|
|
c8b014e457 | |
|
|
b46444dea2 |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -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