Skip to content

Commit 68dd610

Browse files
committed
First commit
0 parents  commit 68dd610

File tree

9 files changed

+627
-0
lines changed

9 files changed

+627
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
out
2+
*.swp
3+
__pycache__
4+

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# look into ssl traffic
2+
3+
credits: https://security.stackexchange.com/questions/80158/extract-pre-master-keys-from-an-openssl-application
4+
5+
## LD_PRELOAD
6+
### How to
7+
1. cc sslkeylog.c -shared -o libsslkeylog.so -fPIC -ldl
8+
2. sudo tcpdump -i any port 443 -w out
9+
3. SSLKEYLOGFILE=premaster.txt LD_PRELOAD=./libsslkeylog.so curl https://heise.de
10+
4. wireshark -o ssl.keylog_file:premaster.txt out
11+

gdb/.gdbinit

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
python
2+
def read_as_hex(name, size):
3+
addr = gdb.parse_and_eval(name).address
4+
data = gdb.selected_inferior().read_memory(addr, size)
5+
return ''.join('%02X' % ord(x) for x in data)
6+
7+
def pm(ssl='s'):
8+
mk = read_as_hex('%s->session->master_key' % ssl, 48)
9+
cr = read_as_hex('%s->s3->client_random' % ssl, 32)
10+
print('CLIENT_RANDOM %s %s' % (cr, mk))
11+
end
12+
13+
#set sysroot /no/such/file
14+
#set solib-search-path /home/kmille/projects/hooking-ssl/gdb/openssl:/usr/lib:/lib
15+
#directory /home/kmille/projects/hooking-ssl/gdb/openssl/
16+
set breakpoint pending on
17+
break SSL_connect
18+
run

gdb/keys.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Automatically generated by sslkeylog.py

gdb/openssl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 50eaac9f3337667259de725451f201e784599687

gdb/sslkeylog.py

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
#!/usr/bin/env python
2+
r'''
3+
Extract SSL/DTLS keys from programs that use OpenSSL.
4+
5+
Example usage, attach to an existing process, put keys in premaster.txt:
6+
7+
PYTHONPATH=. \
8+
gdb -q -ex 'py import sslkeylog as skl; skl.start("premaster.txt")' \
9+
-p `pidof curl`
10+
11+
Run a new program while outputting keys to the default stderr
12+
(you can set envvar SSLKEYLOGFILE to override this):
13+
14+
PYTHONPATH=. gdb -q -ex 'py import sslkeylog as skl; skl.start()' \
15+
-ex r --args curl https://example.com
16+
17+
18+
Recommended configuration: copy this file to ~/.gdb/sslkeylog.py and put
19+
the following in your ~/.gdbinit:
20+
21+
python
22+
import sys, os.path
23+
#sys.dont_write_bytecode = True # avoid *.pyc clutter
24+
sys.path.insert(0, os.path.expanduser('~/.gdb'))
25+
import sslkeylog as skl
26+
# Override default keylog (SSLKEYLOGFILE env or stderr)
27+
#skl.keylog_filename = '/tmp/premaster.txt'
28+
end
29+
30+
define skl-batch
31+
dont-repeat
32+
handle all noprint pass
33+
handle SIGINT noprint pass
34+
py skl.start()
35+
end
36+
37+
Then you can simply execute:
38+
39+
gdb -q -ex 'py skl.start()' -p `pidof curl`
40+
41+
To stop capturing keys, detach GDB or invoke 'skl.stop()'
42+
43+
If you are not interested in debugging the program, and only want to
44+
extract keys, use the skl-batch command defined in gdbinit:
45+
46+
SSLKEYLOGFILE=premaster.txt gdb -batch -ex skl-batch -p `pidof curl`
47+
48+
To stop capturing keys early, send SIGTERM to gdb. (Note that SIGTRAP is
49+
used internally for breakpoints and should not be ignored.)
50+
'''
51+
52+
import gdb
53+
import errno
54+
from os import getenv
55+
56+
# Default filename for new Keylog instances.
57+
#keylog_filename = getenv('SSLKEYLOGFILE', '/dev/stderr')
58+
keylog_filename = getenv('SSLKEYLOGFILE', 'keys.log')
59+
_SSL_KEYLOG_HEADER = '# Automatically generated by sslkeylog.py\n'
60+
61+
def _read_as_hex(value, size):
62+
addr = value.address
63+
data = gdb.selected_inferior().read_memory(addr, size)
64+
return ''.join('%02X' % ord(x) for x in data)
65+
66+
def _ssl_get_master_key(ssl_ptr):
67+
session = ssl_ptr['session']
68+
if session != 0 and session['master_key_length'] > 0:
69+
return _read_as_hex(session['master_key'], 48)
70+
return None
71+
72+
def get_keylog_line(ssl_ptr):
73+
'''
74+
Returns (client_random, master_key) for the current SSL session.
75+
'''
76+
mk = _ssl_get_master_key(ssl_ptr)
77+
s3 = ssl_ptr['s3']
78+
if s3 == 0 or mk is None:
79+
return
80+
81+
cr = _read_as_hex(s3['client_random'], 32)
82+
# Maybe optimize storage by using Session ID if available?
83+
#sid = _read_as_hex(self.ssl_ptr['session']['session_id'], 32)
84+
return (cr, mk)
85+
86+
class SKLFinishBreakpoint(gdb.FinishBreakpoint):
87+
'''Breaks on points where new key material is possibly available.'''
88+
def __init__(self, ssl_ptr, key_listener):
89+
# Mark as internal, it is expected to be gone as soon as this quits.
90+
gdb.FinishBreakpoint.__init__(self, internal=True)
91+
self.ssl_ptr = ssl_ptr
92+
self.key_listener = key_listener
93+
94+
def stop(self):
95+
# Attempt to recover key material.
96+
info = get_keylog_line(self.ssl_ptr)
97+
if info:
98+
# Line consists of a cache key and actual key log line
99+
self.key_listener.notify(*info)
100+
return False # Continue execution
101+
102+
class SKLBreakpoint(gdb.Breakpoint):
103+
'''Breaks at function entrance and registers a finish breakpoint.'''
104+
def __init__(self, spec, key_listener):
105+
gdb.Breakpoint.__init__(self, spec)
106+
self.key_listener = key_listener
107+
108+
def stop(self):
109+
# Retrieve SSL* parameter.
110+
ssl_ptr = gdb.selected_frame().read_var('s')
111+
#ssl_ptr = gdb.selected_frame().read_var('ssl')
112+
113+
# Proceed with handshakes (finish function) before checking for keys.
114+
SKLFinishBreakpoint(ssl_ptr, self.key_listener)
115+
116+
# Increase hit count for debugging (info breakpoints)
117+
# This number will be decremented when execution continues.
118+
self.ignore_count += 1
119+
120+
return False # Continue execution
121+
122+
class Keylog(object):
123+
'''Listens for new key material and writes them to a file.'''
124+
def __init__(self, keylog_file):
125+
self.keylog_file = keylog_file
126+
# Remember written lines to avoid printing duplicates.
127+
self.written_items = set()
128+
129+
def notify(self, client_random, master_key):
130+
'''Puts a new entry in the key log if not already known.'''
131+
if client_random not in self.written_items:
132+
line = 'CLIENT_RANDOM %s %s\n' % (client_random, master_key)
133+
self.keylog_file.write(line.encode('ascii'))
134+
135+
# Assume client random is random enough as cache key.
136+
cache_key = client_random
137+
self.written_items.add(cache_key)
138+
139+
def close():
140+
self.keylog_file.close()
141+
142+
@classmethod
143+
def create(cls, filename):
144+
def needs_header(f):
145+
try:
146+
# Might fail for pipes (such as stdin).
147+
return f.tell() == 0
148+
except:
149+
return False
150+
151+
# Byte output is needed for unbuffered I/O
152+
try:
153+
f = open(filename, 'ab', 0)
154+
except OSError as e:
155+
# Older gdb try to seek when append is requested. If seeking is not
156+
# possible (for stderr or pipes), use plain write mode.
157+
if e.errno == errno.ESPIPE:
158+
f = open(filename, 'wb', 0)
159+
else:
160+
raise
161+
if needs_header(f):
162+
f.write(_SSL_KEYLOG_HEADER.encode('ascii'))
163+
return cls(f)
164+
165+
# A shared Keylog instance.
166+
_keylog_file = None
167+
168+
def start(sslkeylogfile=None, cont=True):
169+
'''
170+
:param sslkeylogfile: optional SSL keylog file name (overrides
171+
SSLKEYLOGFILE environment variable and its fallback value).
172+
:param cont: True to continue this process when paused.
173+
'''
174+
global keylog_filename
175+
if sslkeylogfile:
176+
keylog_filename = sslkeylogfile
177+
enable()
178+
179+
# Continue the process when it was already started before.
180+
if cont and gdb.selected_thread():
181+
gdb.execute('continue')
182+
183+
def stop():
184+
'''Remove all breakpoints and close the key logfile.'''
185+
global _keylog_file
186+
if not _keylog_file:
187+
print('No active keylog session')
188+
return
189+
disable()
190+
_keylog_file.close()
191+
print('Logged %d entries in total' % _keylog_file.written_items)
192+
_keylog_file = None
193+
194+
195+
# Remember enabled breakpoints
196+
_locations = { name: None for name in (
197+
'SSL_connect',
198+
'SSL_do_handshake',
199+
'SSL_accept',
200+
'SSL_read',
201+
'SSL_write',
202+
)}
203+
204+
def enable():
205+
'''Enable all SSL-related breakpoints.'''
206+
global _keylog_file
207+
if not _keylog_file:
208+
_keylog_file = Keylog.create(keylog_filename)
209+
print('Started logging SSL keys to %s' % keylog_filename)
210+
for name, breakpoint in _locations.items():
211+
if breakpoint:
212+
print('Breakpoint for %s is already active, ignoring' % name)
213+
continue
214+
_locations[name] = SKLBreakpoint(name, _keylog_file)
215+
216+
def disable():
217+
'''Disable all SSL-related breakpoints.'''
218+
for name, breakpoint in _locations.items():
219+
if breakpoint:
220+
msg = 'Deleting breakpoint %d' % breakpoint.number
221+
msg += ' (%s)' % breakpoint.location
222+
if breakpoint.hit_count > 0:
223+
msg += ' (called %d times)' % breakpoint.hit_count
224+
print(msg)
225+
breakpoint.delete()
226+
_locations[name] = None

ld_preload/libsslkeylog.so

17.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)