def application_data(plugin): while True: data = memoryview((yield from iofree.read(5))) assert data[ 0] == 0x17, f"{data[0]} != application_data(23) {data.tobytes()}" assert (data[1:3] == plugin.tls_version ), f"{data[1:3].tobytes()} != version({plugin.tls_version})" size = int.from_bytes(data[3:], "big") assert size == size & 0x3FFF, f"{size} is over 2^14" data = yield from iofree.read(size) yield from iofree.write(data)
def tls1_2_request(plugin): parser = yield from iofree.get_parser() tls_version = plugin.tls_version with memoryview((yield from iofree.read(5))) as tls_plaintext_head: assert (tls_plaintext_head[:3] == b"\x16\x03\x01" ), "invalid tls head: handshake(22) protocol_version(3.1)" length = int.from_bytes(tls_plaintext_head[-2:], "big") assert length == length & 0x3FFF, f"{length} is over 2^14" with memoryview((yield from iofree.read(length))) as fragment: assert fragment[ 0] == 1, "expect client_hello(1), but got {fragment[0]}" handshake_length = int.from_bytes(fragment[1:4], "big") client_hello = fragment[4:handshake_length + 4] assert client_hello[:2] == tls_version, "expect: client_version(3.3)" verify_id = client_hello[2:34] # TODO: replay attact detect gmt_unix_time = int.from_bytes(verify_id[:4], "big") time_diff = (int(time()) & 0xFFFFFFFF) - gmt_unix_time assert abs( time_diff) < plugin.time_tolerance, f"expired request: {time_diff}" session_length = client_hello[34] assert session_length >= 32, "session length should be >= 32" session_id = client_hello[35:35 + session_length].tobytes() sha1 = hmac.new(plugin.server.cipher.master_key + session_id, verify_id[:22], hashlib.sha1).digest()[:10] assert verify_id[22:] == sha1, "hmac verify failed" tail = client_hello[35 + session_length:] cipher_suites = tail[:2].tobytes() compression_methods = tail[2:3] (cipher_suites, compression_methods) random_bytes = pack_auth_data(plugin.server.cipher.master_key, session_id) server_hello = (tls_version + random_bytes + session_length.to_bytes(1, "big") + session_id + binascii.unhexlify(b"c02f000005ff01000100")) server_hello = b"\x02\x00" + pack_uint16(server_hello) server_hello = b"\x16" + tls_version + pack_uint16(server_hello) if random.randint(0, 8) < 1: ticket = os.urandom((struct.unpack(">H", os.urandom(2))[0] % 164) * 2 + 64) ticket = struct.pack( ">H", len(ticket) + 4) + b"\x04\x00" + pack_uint16(ticket) server_hello += b"\x16" + tls_version + ticket change_cipher_spec = b"\x14" + tls_version + b"\x00\x01\x01" finish_len = random.choice([32, 40]) change_cipher_spec += (b"\x16" + tls_version + struct.pack(">H", finish_len) + os.urandom(finish_len - 10)) change_cipher_spec += hmac.new( plugin.server.cipher.master_key + session_id, change_cipher_spec, hashlib.sha1).digest()[:10] parser.write(server_hello + change_cipher_spec) yield from ChangeCipherReader(plugin, plugin.server.cipher.master_key, session_id)
def read_some(self): chunk0 = yield from iofree.read(2 + self.cipher.TAG_SIZE) data = memoryview(chunk0) length_bytes = self.decrypt(data[:2], data[2:]) length = int.from_bytes(length_bytes, "big") if length != length & 0x3FFF: raise Exception("invalid length") chunk1 = yield from iofree.read(length + self.cipher.TAG_SIZE) data = memoryview(chunk1) payload = self.decrypt(data[:length], data[length:]) return payload
def _read_some(): parser = yield from iofree.get_parser() chunk0 = yield from iofree.read(2 + parser.cipher.TAG_SIZE) with memoryview(chunk0) as data: length_bytes = parser.decrypt(data[:2], data[2:]) length = int.from_bytes(length_bytes, "big") if length != length & 0x3FFF: # 16 * 1024 - 1 raise Exception("length exceed limit") chunk1 = yield from iofree.read(length + parser.cipher.TAG_SIZE) with memoryview(chunk1) as data: payload = parser.decrypt(data[:length], data[length:]) return payload
def application_data(plugin): parser = yield from iofree.get_parser() while True: with memoryview((yield from iofree.read(5))) as data: assert (data[0] == 0x17 ), f"{data[0]} != application_data(23) {data.tobytes()}" assert ( data[1:3] == plugin.tls_version ), f"{data[1:3].tobytes()} != version({plugin.tls_version})" size = int.from_bytes(data[3:], "big") assert size == size & 0x3FFF, f"{size} is over 2^14" data = yield from iofree.read(size) parser.respond(result=data)
def socks5_response(auth): data = yield from iofree.read(2) assert data[0] == 5, f"bad socks version: {data[0]}" method = data[1] assert method in (0, 2), f"bad method {data[1]}" if auth: auth_ver, status = yield from iofree.read_struct("!BB") assert auth_ver == 1, f"invalid auth version {auth_ver}" assert status == 0, f"invalid status {status}" data = yield from iofree.read(3) assert data[0] == 5, f"bad socks version: {data[0]}" assert data[1] == 0, f"failed REP with code: {data[1]}" bind_addr = yield from read_addr() return bind_addr
def ChangeCipherReader(plugin, key, session_id): with memoryview((yield from iofree.read(11))) as data: assert data[ 0] == 0x14, f"{data[0]} != change_cipher_spec(20) {data.tobytes()}" assert (data[1:3] == plugin.tls_version ), f"{data[1:3].tobytes()} != version({plugin.tls_version})" assert data[3:6] == b"\x00\x01\x01", "bad ChangeCipherSpec" assert data[6] == 0x16, f"{data[6]} != Finish(22)" assert (data[7:9] == plugin.tls_version ), f"{data[7:9]} != version({plugin.tls_version})" assert data[9] == 0x00, f"{data[9]} != Finish(0)" verify_len = int.from_bytes(data[9:11], "big") with memoryview((yield from iofree.read(verify_len))) as verify: sha1 = hmac.new(key + session_id, b"".join([data, verify[:-10]]), hashlib.sha1).digest()[:10] assert sha1 == verify[-10:], "hmac verify failed"
def read_addr(): atyp = yield from iofree.read(1) if atyp == b"\x01": # IPV4 data = yield from iofree.read(4) host = socket.inet_ntoa(data) elif atyp == b"\x04": # IPV6 data = yield from iofree.read(16) host = socket.inet_ntop(socket.AF_INET6, data) elif atyp == b"\x03": # hostname data = yield from iofree.read(1) data += yield from iofree.read(data[0]) host = data[1:].decode("ascii") else: raise Exception(f"unknown atyp: {atyp}") port, = yield from iofree.read_struct("!H") return (host, port)
def ss_reader(cipher): parser = yield from iofree.get_parser() iv = yield from iofree.read(cipher.IV_SIZE) decrypt = cipher.make_decrypter(iv) while True: data = yield from iofree.read_more() parser.write(decrypt(data))
def aead_reader(cipher): parser = yield from iofree.get_parser() parser.cipher = cipher salt = yield from iofree.read(cipher.SALT_SIZE) parser.decrypt = cipher.make_decrypter(salt) while True: payload = yield from _read_some() parser.write(payload)
def http_response(): response = yield from HTTPResponse assert response.ver == b"HTTP/1.1" assert response.code == b"200" assert response.status == b"OK" yield from iofree.read_until(b"\n", return_tail=True) data = yield from iofree.read(4) assert data == b"haha" (number, ) = yield from iofree.read_struct("!H") assert number == 8 * 256 + 8 number = yield from iofree.read_int(3) assert number == int.from_bytes(b"\x11\x11\x11", "big") assert (yield from iofree.peek(2)) == b"co" assert (yield from iofree.read(7)) == b"content" yield from iofree.wait() assert len((yield from iofree.read_more(5))) >= 5 yield from iofree.read() yield from iofree.wait_event() return b"\r\n".join(response.header_lines)
def tls1_2_response(plugin): tls_version = plugin.tls_version with memoryview((yield from iofree.read(5))) as tls_plaintext_head: assert (tls_plaintext_head[:3] == b"\x16\x03\x03" ), "invalid tls head: handshake(22) protocol_version(3.1)" length = int.from_bytes(tls_plaintext_head[-2:], "big") assert length == length & 0x3FFF, f"{length} is over 2^14" with memoryview((yield from iofree.read(length))) as fragment: assert fragment[ 0] == 2, f"expect server_hello(2), bug got: {fragment[0]}" handshake_length = int.from_bytes(fragment[1:4], "big") server_hello = fragment[4:handshake_length + 4] assert server_hello[:2] == tls_version, "expect: server_version(3.3)" verify_id = server_hello[2:34] sha1 = hmac.new( plugin.client.ns.cipher.master_key + plugin.session_id, verify_id[:-10], hashlib.sha1, ).digest()[:10] assert sha1 == verify_id[-10:], "hmac verify failed" assert server_hello[34] == 32, f"expect 32, but got {server_hello[34]}" # verify_id = server_hello[35:67] # sha1 = hmac.new( # plugin.client.ns.cipher.master_key + plugin.session_id, # fragment[:-10], # hashlib.sha1, # ).digest()[:10] # assert sha1 == fragment[-10:], "hmac verify failed" while True: x = yield from iofree.peek(1) if x[0] != 22: break with memoryview((yield from iofree.read(5))) as ticket_head: length = int.from_bytes(ticket_head[-2:], "big") assert length == length & 0x3FFF, f"{length} is over 2^14" yield from iofree.read(length) yield from ChangeCipherReader(plugin, plugin.client.ns.cipher.master_key, plugin.session_id) yield from application_data(plugin)
def socks5_request(auth=False): parser = yield from iofree.get_parser() ver, nmethods = yield from iofree.read_struct("!BB") assert ver == 5, f"bad socks version: {ver}" assert nmethods != 0, f"nmethods can't be 0" methods = yield from iofree.read(nmethods) if auth and b"\x02" not in methods: parser.write(b"\x05\x02") raise Exception("server needs authentication") elif b"\x00" not in methods: parser.write(b"\x05\x00") raise Exception("method not support") if auth: parser.write(b"\x05\x02") auth_ver, username_length = yield from iofree.read_struct("!BB") assert auth_ver == 1, f"invalid auth version {auth_ver}" username = yield from iofree.read(username_length) password_length = (yield from iofree.read(1))[0] password = yield from iofree.read(password_length) if (username, password) != auth: parser.write(b"\x01\x01") raise Exception("authenticate failed") else: parser.write(b"\x01\x00") else: parser.write(b"\x05\x00") ver, cmd, rsv = yield from iofree.read_struct("!BBB") if cmd == 1: # connect pass elif cmd == 2: # bind raise Exception("doesn't support bind yet") elif cmd == 3: # associate raise Exception("doesn't support associate yes") else: raise Exception(f"unknown cmd: {cmd}") target_addr = yield from read_addr() return target_addr, cmd
def addr_reader(): atyp = yield from iofree.read(1) if atyp == b"\x01": # IPV4 data = yield from iofree.read(4) host = socket.inet_ntoa(data) elif atyp == b"\x04": # IPV6 data = yield from iofree.read(16) host = socket.inet_ntop(socket.AF_INET6, data) elif atyp == b"\x03": # hostname data = yield from iofree.read(1) data += yield from iofree.read(data[0]) host = data[1:].decode("ascii") else: raise Exception(f"unknown atyp: {atyp}") data_port = yield from iofree.read(2) port = int.from_bytes(data_port, "big") return (host, port), atyp + data + data_port
def tls_response(self): parser = yield from iofree.get_parser() while True: head = yield from iofree.read(5) assert head[ 1:3] == b"\x03\x03", f"bad legacy_record_version {head[1:3]}" length = int.from_bytes(head[3:], "big") if (head[0] == ContentType.application_data and length > (16384 + 256)) or (head[0] != ContentType.application_data and length > 16384): parser.write(self.pack_fatal(AlertDescription.record_overflow)) raise Alert(AlertLevel.fatal, AlertDescription.record_overflow) content = memoryview((yield from iofree.read(length))) if head[0] == ContentType.alert: level = AlertLevel.from_value(content[0]) description = AlertDescription.from_value(content[1]) raise Alert(level, description) elif head[0] == ContentType.handshake: self.peer_handshake = self.unpack_handshake(content) assert (self.peer_handshake.handshake_type == HandshakeType.server_hello), "expect server hello" peer_pk = self.peer_handshake.extensions[ ExtensionType.key_share].key_exchange shared_key = crypto_scalarmult(bytes(self.private_key), peer_pk) TLSCipher = self.peer_handshake.cipher_suite self.TLSCipher = TLSCipher key_index = self.peer_handshake.extensions.get( ExtensionType.pre_shared_key) psk = None if key_index is None else self.psk_list[key_index] key_scheduler = TLSCipher.tls_hash.scheduler(shared_key, psk) self.key_scheduler = key_scheduler secret = key_scheduler.server_handshake_traffic_secret( self.handshake_context) # server handshake cipher self.peer_cipher = TLSCipher(secret) client_handshake_traffic_secret = key_scheduler.client_handshake_traffic_secret( self.handshake_context) elif head[0] == ContentType.application_data: plaintext = self.peer_cipher.decrypt(content, head).rstrip(b"\x00") content_type = ContentType.from_value(plaintext[-1]) if content_type == ContentType.handshake: self.unpack_handshake(plaintext[:-1]) if self.server_finished: if self.early_data: eoe_data = HandshakeType.end_of_early_data.pack_data( b"") # self.handshake_context.extend(eoe_data) inner_plaintext = ContentType.handshake.tls_inner_plaintext( eoe_data) record = self.cipher.tls_ciphertext( inner_plaintext) parser.write(record) # client handshake cipher cipher = TLSCipher(client_handshake_traffic_secret) client_finished = cipher.verify_data( self.handshake_context) client_finished_data = HandshakeType.finished.pack_data( client_finished) inner_plaintext = ContentType.handshake.tls_inner_plaintext( client_finished_data) record = cipher.tls_ciphertext(inner_plaintext) change_cipher_spec = ContentType.change_cipher_spec.tls_plaintext( b"\x01") parser.write(change_cipher_spec + record) # server application cipher server_secret = key_scheduler.server_application_traffic_secret_0( self.handshake_context) self.peer_cipher = TLSCipher(server_secret) self.server_finished = False # client application cipher client_secret = key_scheduler.client_application_traffic_secret_0( self.handshake_context) self.cipher = TLSCipher(client_secret) self.handshake_context.extend(client_finished_data) elif content_type == ContentType.application_data: self.data_callback(plaintext[:-1]) elif content_type == ContentType.alert: level = AlertLevel.from_value(plaintext[0]) description = AlertDescription.from_value(plaintext[1]) raise Alert(level, description) elif content_type == ContentType.invalid: raise Exception("invalid content type") else: raise Exception(f"unexpected content type {content_type}") elif head[0] == ContentType.change_cipher_spec: assert content == b"\x01", "change_cipher should be 0x01" else: raise Exception(f"Unknown content type: {head[0]}")
def reader(self): salt = yield from iofree.read(self.cipher.SALT_SIZE) self.decrypt = self.cipher.make_decrypter(salt) while True: payload = yield from self.read_some() yield from iofree.write(payload)
def tls_response(self): while True: head = yield from iofree.read(5) assert head[ 1:3] == b"\x03\x03", f"bad legacy_record_version {head[1:3]}" length = int.from_bytes(head[3:], "big") content = memoryview((yield from iofree.read(length))) if head[0] == ContentType.alert: level = AlertLevel.from_value(content[0]) description = AlertDescription.from_value(content[1]) raise Alert(level, description) elif head[0] == ContentType.handshake: self.peer_handshake = self.unpack_handshake(content) assert (self.peer_handshake.handshake_type == HandshakeType.server_hello), "expect server hello" peer_pk = self.peer_handshake.extensions[ ExtensionType.key_share].key_exchange shared_key = crypto_scalarmult(bytes(self.private_key), peer_pk) TLSCipher = self.peer_handshake.cipher_suite key_scheduler = TLSCipher.tls_hash.scheduler(shared_key) secret = key_scheduler.server_handshake_traffic_secret( self.get_context()) # server handshake cipher self.peer_cipher = TLSCipher(secret) client_handshake_traffic_secret = key_scheduler.client_handshake_traffic_secret( self.get_context()) elif head[0] == ContentType.application_data: plaintext = self.peer_cipher.decrypt(content, head).rstrip(b"\x00") content_type = ContentType.from_value(plaintext[-1]) if content_type == ContentType.handshake: self.unpack_handshake(plaintext[:-1]) if self.server_finished: # client handshake cipher self.cipher = TLSCipher( client_handshake_traffic_secret) context = b"".join(self.handshake_context) client_finished = self.cipher.verify_data(context) inner_plaintext = HandshakeType.finished.tls_inner_plaintext( client_finished) record = self.cipher.tls_ciphertext(inner_plaintext) change_cipher_spec = ContentType.change_cipher_spec.tls_plaintext( b"\x01") yield from iofree.write(change_cipher_spec + record) # server application cipher server_secret = key_scheduler.server_application_traffic_secret_0( self.get_context()) self.peer_cipher = TLSCipher(server_secret) self.server_finished = False # client application cipher client_secret = key_scheduler.client_application_traffic_secret_0( self.get_context()) self.cipher = TLSCipher(client_secret) elif content_type == ContentType.application_data: yield from iofree.write(plaintext[:-1]) elif content_type == ContentType.alert: level = AlertLevel.from_value(plaintext[0]) description = AlertDescription.from_value(plaintext[1]) raise Alert(level, description) elif content_type == ContentType.invalid: raise Exception("invalid content type") else: raise Exception(f"unexpected content type {content_type}") elif head[0] == ContentType.change_cipher_spec: assert content == b"\x01", "change_cipher should be 0x01" else: raise Exception(f"Unknown content type: {head[0]}")
def simple(): parser = yield from iofree.get_parser() yield from iofree.read(1) assert parser.has_more_data() assert not parser.finished() raise Exception("special")