forked from matthiasbock/OpenSkype
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFluxCapacitor.py
executable file
·355 lines (319 loc) · 13.1 KB
/
FluxCapacitor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#!/usr/bin/python
# Copyright (C) 2010 Dr. Ralf Schlatterbeck Open Source Consulting.
# Reichergasse 131, A-3411 Weidling.
# Web: http://www.runtux.com Email: [email protected]
# All rights reserved
# ****************************************************************************
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Note that the shared lib we're calling here is probably not GPL
# compatible. Use at your own risk.
# Wrapper for the recently revealed skype obfuscation layer from
# http://cryptolib.com/ciphers/skype/ -- see blog article
# http://www.enrupt.com/index.php/2010/07/07/skype-biggest-secret-revealed
# To run this you need the ctypes library for python (included in recent
# python releases) and the libpcap wrapper for python (debian package
# python-libpcap)
from struct import pack, unpack
#from binascii import crc32 # same as zlib
from zlib import crc32
from ctypes import CDLL, c_uint8, c_uint32, c_char_p, c_void_p, c_char
from ctypes import POINTER, pointer, Structure
# Set LD_LIBRARY_PATH appropriately if this fails for you:
dll = CDLL ("./skype_rc4.so")
class RC4_Context (Structure) :
""" RC4_Context used internally by the Skype_RC4_Expand_IV (which
initializes the structure) and RC4_crypt routines.
"""
_fields_ = (('i', c_uint8), ('j', c_uint8), ('s', c_uint8 * 256))
# end class RC4_Context
dll.RC4_crypt.restype = None
dll.RC4_crypt.argtypes = \
(c_char_p, c_uint32, POINTER (RC4_Context), c_uint32)
dll.Skype_RC4_Expand_IV.restype = None
dll.Skype_RC4_Expand_IV.argtypes = \
(c_uint32, c_void_p, POINTER (RC4_Context), c_uint32, c_uint32)
def Skype_RC4_Expand_IV (iv, rc4_context, iv2 = '', flags = 0) :
""" Main RC4 IV expansion function, matching Skype parameters, with
optional IV2 for the latest DH-384 based protocol
This is a convenience wrapper, note that the parameters have
been reordered to reflect the case where we don't want to
specify iv2 (in fact the publicly available info currently
doesn't tell us how to use the new DH-384 based protocol
mentioned in the original docs, if you know more, please contact
me at [email protected])
"""
return dll.Skype_RC4_Expand_IV (iv, iv2, rc4_context, flags, len (iv2))
# end def Skype_RC4_Expand_IV
def RC4_crypt (buffer, rc4_context, test = 0) :
""" RC4 encrypt/decrypt (test=1 leaves rc4 context unaltered)
This is a convenience wrapper.
"""
buflen = len (buffer)
b = (c_char * buflen)()
for n, s in enumerate (buffer) :
b [n] = s
dll.RC4_crypt (b, buflen, rc4_context, test)
return b [0:buflen]
# end def RC4_crypt
def ascii (s) :
if len (repr (s)) > 3 :
return '.'
return s
# end def ascii
def dumphex (s) :
r = []
for x in xrange (len (s) / 16 + 1) :
hex = '%-48s' % ' '.join ("%02x" % ord (k) for k in s [x*16:(x+1)*16])
char = '%-16s' % ''.join (ascii (k) for k in s [x*16:(x+1)*16])
r.append (' '.join ((hex, char)))
return '\n'.join (r)
# end def dumphex
def skype_crc (s, seed = 0xFFFFFFFF) :
""" Emulate crc function as used by skype -- the ethernet crc32
(also used in pkzip and others) xors the result with 0xFFFFFFFF
so we have to reverse that
>>> x = crc32 ('abcd')
>>> x = crc32 ('fghi', x)
>>> x & 0xFFFFFFFF
1292354512L
>>> x = crc32 ('abcdfghi')
>>> x & 0xFFFFFFFF
1292354512L
>>> x = emulated_crc ('abcd')
>>> x
310194926L
>>> 0x127D32EEL
310194926L
>>> x = emulated_crc ('fghi', x)
>>> x
3002612783L
>>> 0xB2F83C2FL
3002612783L
>>> x = emulated_crc ('abcdfghi')
>>> x
3002612783L
>>> x = emulated_crc ('\xff\xf0\xf1\xf2')
>>> x
3949860366L
>>> s = ''.join (chr (x) for x in xrange (0x80, 0x100))
>>> x = emulated_crc (s)
>>> x
3115716450L
"""
return crc32 (s, seed ^ 0xFFFFFFFF) ^ 0xFFFFFFFF
# end def emulated_crc
def Seed(src, dst, topic, seed = 0xFFFFFFFF):
seed = skype_crc (''.join (reversed (src)), seed)
seed = skype_crc (''.join (reversed (dst)), seed)
seed = skype_crc (''.join (reversed (topic)), seed)
seed = skype_crc ('\x00\x00', seed)
return seed
class Skype_Decryptor (object) :
def __init__ (self, initial_data = None, iv = None) :
""" Initialize from initial tcp packet or from given iv """
self.restart = False
if initial_data :
self.init_tcp (initial_data)
else :
self.iv = iv
self.restart = True
# end def __init__
def init_tcp (self, sky) :
""" Dissect start of TCP connection. Silver Needle tells us that
a TCP stream starts with the skype IV followed by 10 bytes
which should decrypt to 00 01 00 00 00 01 00 00 00 01
"""
assert (len (sky) >= 14)
#print dumphex (sky [:4])
# For TCP Skype seems to put the IV into the TCP stream as the
# first 4 bytes. We unpack as network byte order (!)
iv = unpack ('!L', sky [:4]) [0]
#print "%08x" % iv
rc4 = RC4_Context ()
#print dumphex (sky [4:])
Skype_RC4_Expand_IV (iv, rc4)
r = RC4_crypt (sky [4:], rc4, test = 1)
assert (r.startswith ('\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01'))
#print dumphex (r)
self.rc4 = rc4
self.iv = iv
# end def init_tcp
def decrypt_tcp (self, data, restart = False) :
""" Decrypt next TCP packet. Note that restart should not be
necessary -- we decrypt the initial 10 byte sequence with
test=1 parameter for RC4_crypt, so the rc4 internal state is
not updated.
We do not know yet if both sides of the TCP connection use
the same key devived from the same iv or if different keys
(how would these be exchanged?) are used in each direction.
Probably the same key is used in both directions set up with
the same iv. That means we need another instance of this
class for the other direction. Just initialize with the iv
from the other direction...
"""
if restart or self.restart :
Skype_RC4_Expand_IV (self.iv, self.rc4)
self.restart = False
return RC4_crypt (data, self.rc4)
# end def decrypt_tcp
def decrypt_udp (self, ip, src = None, dst = None) :
""" Dissect a UDP packet. Silver Needle tells us that a UDP
packet starts with a two-byte magic number (called id)
followed by a one-byte opcode (called func), a 4-byte CRC32
of the decrypted contents (to verify correct reception) and
then the encrypted contents.
We get the full IP packet without the Ethernet part.
EITHER USE a packet with a correct *EXTERNAL* ip in it
or specify the correct ip as src!
Also be sure the dst ist the *final* destination.
"""
assert (ord (ip [0]) & 0xf0 == 0x40) # IPv4
assert (ord (ip [9]) == 0x11) # UDP
ipo = 4 * (ord (ip [0]) & 0xf)
udpo = ipo + 8
skypo = udpo + 3 # start of iv, crc, encrypted data
skid = ip [udpo:udpo+2]
newdst = None
if (ord (ip [udpo+2]) & 0xf) == 0x3 :
assert (ord (ip [udpo+3]) == 0x01)
newdst = ip [udpo+4:udpo+8]
skypo = udpo + 8
if src :
if len (src) > 4 :
src = ''.join (chr (int (x)) for x in src.split ('.'))
else :
src = ip [12:16]
if dst :
if len (dst) > 4 :
dst = ''.join (chr (int (x)) for x in dst.split ('.'))
else :
if newdst :
dst = newdst
else :
dst = ip [16:20]
seed = Seed(src, dst, skid)
print "seed: %08x" % seed
iv = unpack ('!L', ip [skypo:skypo+4]) [0] & 0xFFFFFFFF
crc = unpack ('!L', ip [skypo+4:skypo+8]) [0] & 0xFFFFFFFF
# print "expect CRC: %08x" % crc
seed = (seed ^ iv) & 0xFFFFFFFF
print "seed ^ iv: %08x" % seed
self.rc4 = RC4_Context ()
Skype_RC4_Expand_IV (seed, self.rc4)
# print dumphex (ip [skypo+8:])
plaintext = RC4_crypt (ip [skypo+8:], self.rc4)
# print dumphex (plaintext)
# print "got CRC: %08x\n" % (skype_crc (plaintext) & 0xFFFFFFFF)
return plaintext
# end def decrypt_udp
# end class Skype_Decryptor
if __name__ == '__main__' :
# Get a packet from pcap and try to unpack and verify CRC
# I'm using a pcap file containing Skype traffic from
# wireshark.org:
# http://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=SkypeIRC.cap
import pcap
count = 0
decryptor = None
def get_tcp_payload (data) :
""" Extract payload from a tcp packet """
assert (data [12:14] == '\x08\x00') # IP
assert (ord (data [14]) & 0xf0 == 0x40) # IPv4
assert (data [23] == '\x06') # TCP
hl = 4 * (ord (data [14]) & 0xf) + 14
tcp = data [hl:]
thl = (ord (tcp [12]) & 0xf0) >> 2
return tcp [thl:]
# end def get_tcp_payload
def cb (pktlen, data, timestamp) :
""" callback for packet dump
"""
global count
global decryptor
count += 1
# FIXME: We really want to follow the tcp stream in both
# directions here and decrypt that. Not done for now.
if count == 273 :
print
sky = get_tcp_payload (data)
print 'tcp payload: '+dumphex(sky)
decryptor = Skype_Decryptor (sky)
print '4 bytes seed: '+dumphex(sky[:4])
# display that the first bytes are really as expected from
# Silver Needle in the Skype:
# After this we need to restart, this won't be necessary if
# we didn't decrypt the first 14 bytes again.
print 'decrypt 10 bytes: '+dumphex(decryptor.decrypt_tcp (sky [4:14], True))
decryptor.restart = True
# Got more to decrypt?
if len (sky) > 14 :
d = decryptor.decrypt_tcp (sky [14:])
print
elif count == 274 and 0 :
sky = get_tcp_payload (data)
d = decryptor.decrypt_tcp (sky)
print dumphex (d)
d = decryptor.decrypt_tcp (sky, restart = True)
print dumphex (d)
elif count == 216 and 0 :
# 0x03 skype forwarding packet, not yet working
assert (data [12:14] == '\x08\x00') # IP
sky = data [14:]
decryptor = Skype_Decryptor ()
print "------------"
d = decryptor.decrypt_udp (sky, '\x56\x80\xF5\x73', '\x24\x49\x9e\xb8')
d = decryptor.decrypt_udp (sky, '\x56\x80\xF5\x73')
d = decryptor.decrypt_udp (sky, '\x56\x80\xF5\x73', '\xa5\x7c\xfd\xf1')
d = decryptor.decrypt_udp (sky, '\x24\x49\x9e\xb8', '\xa5\x7c\xfd\xf1')
d = decryptor.decrypt_udp (sky, '\xa5\x7c\xfd\xf1', '\x24\x49\x9e\xb8')
print "------------"
elif count == 220 :
assert (data [12:14] == '\x08\x00') # IP
sky = data [14:]
decryptor = Skype_Decryptor ()
d = decryptor.decrypt_udp (sky, '\x56\x80\xF5\x73')
elif count == 419 :
assert (data [12:14] == '\x08\x00') # IP
sky = data [14:]
decryptor = Skype_Decryptor ()
# other direction, set dst to the external IP
d = decryptor.decrypt_udp (sky, dst = '\x56\x80\xF5\x73')
# end def cb
# Silver Needle example p. 41 is obviously a packet *before* Skype
# knows its external IP address, so the external IP used is 0.0.0.0
# the 172.16 address is RFC1918
# This is the same as Vanilla Skype part 1 p. 49
sky = \
('\x45\x00\x00\x2e\x00\x04\x40\x00\x40\x11\xeb\x75'
'\xac\x10\x48\x83\x18\x62\x42\x50\x08\x03\x20\x53'
'\x00\x1a\x21\x9c\x7f\x4e\x02\x11\x8a\xc0\x37\xfc'
'\x95\x75\x5e\x5e\xb9\x81\x7a\x8e\xfa\x81'
)
decryptor = Skype_Decryptor ()
d = decryptor.decrypt_udp (sky, '\0\0\0\0')
# Another initial udp packet from Vanilla or Silver Needle (where
# did I get this one from) before skype knows its external IP
sky = \
('\x45\x00\x00\x2e\x00\x04\x40\x00\x40\x11\xeb\x75'
'\xac\x10\x48\x83\xd4\x46\xcc\xd1\x08\x03\x20\x53'
'\x00\x1a\x21\x9c\x7f\x46\x02\x09\x37\x63\xfb\xf2'
'\x86\x24\xe6\x9a\x83)\x08K\xc6\xa8'
)
decryptor = Skype_Decryptor ()
d = decryptor.decrypt_udp (sky, '\0\0\0\0')
p = pcap.pcapObject ()
p.open_offline ('SkypeIRC.cap')
p.loop (419, cb)