diff --git a/entrance_server.go b/entrance_server.go new file mode 100644 index 000000000..93d98007d --- /dev/null +++ b/entrance_server.go @@ -0,0 +1,58 @@ +package main + +import ( + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "net" + + "github.com/Andoryuuta/Erupe/network" +) + +func handleEntranceServerConnection(conn net.Conn) { + // Client initalizes the connection with a one-time buffer of 8 NULL bytes. + nullInit := make([]byte, 8) + n, err := io.ReadFull(conn, nullInit) + if err != nil { + fmt.Println(err) + return + } else if n != len(nullInit) { + fmt.Println("io.ReadFull couldn't read the full 8 byte init.") + return + } + + cc := network.NewCryptConn(conn) + for { + pkt, err := cc.ReadPacket() + if err != nil { + return + } + + fmt.Printf("Got entrance server command:\n%s\n", hex.Dump(pkt)) + + data, err := ioutil.ReadFile("tw_server_list_resp.bin") + if err != nil { + print(err) + return + } + cc.SendPacket(data) + + } +} + +func doEntranceServer(listenAddr string) { + l, err := net.Listen("tcp", listenAddr) + if err != nil { + panic(err) + } + defer l.Close() + + for { + conn, err := l.Accept() + if err != nil { + panic(err) + } + go handleEntranceServerConnection(conn) + } +} diff --git a/main.go b/main.go index fdc7994d7..38feb380f 100644 --- a/main.go +++ b/main.go @@ -2,16 +2,14 @@ package main import ( "fmt" - "io" - _ "time" - - "github.com/Andoryuuta/Erupe/network" + "time" ) func main() { fmt.Println("Starting!") go serveLauncherHTML(":80") + go doEntranceServer(":53310") go doSignServer(":53312") for { diff --git a/network/crypt_conn.go b/network/crypt_conn.go index 6ccfae876..7cce26d37 100644 --- a/network/crypt_conn.go +++ b/network/crypt_conn.go @@ -69,10 +69,40 @@ func (cc *CryptConn) ReadPacket() ([]byte, error) { } _ = combinedCheck - /* - fmt.Printf("cc %X, c0 %X, c1 %X, c2 %X\n", combinedCheck, check0, check1, check2) - fmt.Printf("cc %X, c0 %X, c1 %X, c2 %X\n", cph.PrevPacketCombinedCheck, cph.Check0, cph.Check1, cph.Check2) - fmt.Printf("cph: %+v\n", cph) - */ return out, nil } + +// SendPacket encrypts and sends a packet. +func (cc *CryptConn) SendPacket(data []byte) error { + keyRotDelta := byte(3) + + if keyRotDelta != 0 { + cc.sendKeyRot = (uint32(keyRotDelta) * (cc.sendKeyRot + 1)) + } + + // Encrypt the data + encData, combinedCheck, check0, check1, check2 := crypto.Encrypt(data, cc.sendKeyRot, nil) + + header := &CryptPacketHeader{} + header.Pf0 = byte(((uint(len(encData)) >> 12) & 0xF3) | 3) + header.KeyRotDelta = keyRotDelta + header.PacketNum = uint16(cc.sentPackets) + header.DataSize = uint16(len(encData)) + header.PrevPacketCombinedCheck = cc.prevSendPacketCombinedCheck + header.Check0 = check0 + header.Check1 = check1 + header.Check2 = check2 + + headerBytes, err := header.Encode() + if err != nil { + return err + } + + cc.conn.Write(headerBytes) + cc.conn.Write(encData) + + cc.sentPackets++ + cc.prevSendPacketCombinedCheck = combinedCheck + + return nil +} diff --git a/network/crypt_packet.go b/network/crypt_packet.go index 0a5707a85..ae556d42e 100644 --- a/network/crypt_packet.go +++ b/network/crypt_packet.go @@ -65,3 +65,26 @@ func NewCryptPacketHeader(data []byte) (*CryptPacketHeader, error) { return &c, nil } + +// Encode encodes the CryptPacketHeader into raw bytes. +func (c *CryptPacketHeader) Encode() ([]byte, error) { + buf := bytes.NewBuffer([]byte{}) + var data = []interface{}{ + c.Pf0, + c.KeyRotDelta, + c.PacketNum, + c.DataSize, + c.PrevPacketCombinedCheck, + c.Check0, + c.Check1, + c.Check2, + } + for _, v := range data { + err := binary.Write(buf, binary.BigEndian, v) + if err != nil { + return nil, err + } + } + + return buf.Bytes(), nil +} diff --git a/sign_server.go b/sign_server.go index 67ca3be39..0e267b1b8 100644 --- a/sign_server.go +++ b/sign_server.go @@ -9,6 +9,77 @@ import ( "github.com/Andoryuuta/byteframe" ) +func paddedString(x string, size uint) []byte { + out := make([]byte, size) + copy(out, x) + + // Null terminate it. + out[len(out)-1] = 0 + return out +} + +func uint8PascalString(bf *byteframe.ByteFrame, x string) { + bf.WriteUint8(uint8(len(x) + 1)) + bf.WriteNullTerminatedBytes([]byte(x)) +} + +func uint16PascalString(bf *byteframe.ByteFrame, x string) { + bf.WriteUint16(uint16(len(x) + 1)) + bf.WriteNullTerminatedBytes([]byte(x)) +} + +func makeSignInResp(username string) []byte { + bf := byteframe.NewByteFrame() + + bf.WriteUint8(1) // resp_code + bf.WriteUint8(0) // file/patch server count + bf.WriteUint8(1) // entrance server count + bf.WriteUint8(1) // character count + bf.WriteUint32(0xFFFFFFFF) // login_token_number + bf.WriteBytes(paddedString("logintokenstrng", 16)) // login_token (16 byte padded string) + bf.WriteUint32(1576761190) + + // file patch server PascalStrings here + + // Array(this.entrance_server_count, PascalString(Byte, "utf8")), + uint8PascalString(bf, "localhost:53310") + + // Characters: + bf.WriteUint32(1039336769) // character ID + bf.WriteUint16(30) + bf.WriteUint16(7) + bf.WriteUint32(1576761172) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteUint8(0) + bf.WriteBytes(paddedString("username", 16)) + bf.WriteBytes(paddedString("", 32)) + + bf.WriteUint8(0) // friends_list_count + bf.WriteUint8(0) // guild_members_count + bf.WriteUint8(0) // notice_count + bf.WriteUint32(0xDEADBEEF) // some_last_played_character_id + bf.WriteUint32(14) // unk_flags + uint8PascalString(bf, "") // unk_data_blob PascalString + + bf.WriteUint16(51728) + bf.WriteUint16(20000) + uint16PascalString(bf, "1000672925") + + bf.WriteUint8(0) + + bf.WriteUint16(51729) + bf.WriteUint16(1) + bf.WriteUint16(20000) + uint16PascalString(bf, "203.191.249.36:8080") + + bf.WriteUint32(1578905116) + bf.WriteUint32(0) + + return bf.Data() +} + func handleSignServerConnection(conn net.Conn) { // Client initalizes the connection with a one-time buffer of 8 NULL bytes. nullInit := make([]byte, 8) @@ -25,7 +96,7 @@ func handleSignServerConnection(conn net.Conn) { for { pkt, err := cc.ReadPacket() if err != nil { - panic(err) + return } bf := byteframe.NewByteFrameFromBytes(pkt) @@ -33,9 +104,11 @@ func handleSignServerConnection(conn net.Conn) { username := string(bf.ReadNullTerminatedBytes()) password := string(bf.ReadNullTerminatedBytes()) unk := string(bf.ReadNullTerminatedBytes()) - fmt.Println("Got signin, type: %s, username: %s, password %s, unk: %s", loginType, username, password, unk) + fmt.Printf("Got signin, type: %s, username: %s, password %s, unk: %s", loginType, username, password, unk) + + resp := makeSignInResp(username) + cc.SendPacket(resp) - //fmt.Printf("Got:\n%s", hex.Dump(pkt)) } } diff --git a/test.py b/test.py index 6bc1afc50..5e6e70e4d 100644 --- a/test.py +++ b/test.py @@ -68,6 +68,9 @@ class PacketCrypto(object): t_idx = data[i] ^ SHARED_CRYPT_KEY[shared_buf_idx] dec_key_byte = DECRYPT_KEY[t_idx] shared_buf_idx = ((unk_derived_cryptkey_rot >> 10) ^ dec_key_byte) & 0xFF + print('t_idx: {:X}'.format(t_idx)) + print('i & 7: {:X}'.format(i & 7)) + # Update the checksum accumulators. accumulator_0 = (accumulator_0 + ((t_idx << (i & 7)) & 0xFFFFFFFF)) @@ -90,8 +93,91 @@ class PacketCrypto(object): return (output_data, combined_check, check_0, check_1, check_2) - +""" with open(sys.argv[1], 'rb') as f: (output_data, combined_check, check_0, check_1, check_2) = PacketCrypto._general_crypt(f.read(), int(sys.argv[2], 16), 1) hexdump(output_data) - print("cc {:x}, c0 {:x}, c1 {:x}, c2 {:x}".format(combined_check, check_0, check_1, check_2)) \ No newline at end of file + print("cc {:x}, c0 {:x}, c1 {:x}, c2 {:x}".format(combined_check, check_0, check_1, check_2)) +""" + +from construct import * + +Binary8Header = Struct( + "server_type" / Bytes(3), + "entry_count" / Int16ub, + "body_size" / Int16ub, + "checksum" / Int32ub, +) + +BINARY8_KEY = bytes([0x01, 0x23, 0x34, 0x45, 0x56, 0xAB, 0xCD, 0xEF]) +def decode_binary8(data, unk_key_byte): + cur_key = ((54323 * unk_key_byte) + 1) & 0xFFFFFFFF + + output_data = bytearray() + for i in range(len(data)): + tmp = (data[i] ^ (cur_key >> 13)) & 0xFF + output_data.append(tmp ^ BINARY8_KEY[i&7]) + cur_key = ((54323 * cur_key) + 1) & 0xFFFFFFFF + + return output_data + +def encode_binary8(data, unk_key_byte): + cur_key = ((54323 * unk_key_byte) + 1) & 0xFFFFFFFF + + output_data = bytearray() + for i in range(len(data)): + output_data.append(data[i] ^ (BINARY8_KEY[i&7] ^ ((cur_key >> 13) & 0xFF))) + cur_key = ((54323 * cur_key) + 1) & 0xFFFFFFFF + + return output_data + + +SUM32_TABLE_0 = bytes([0x35, 0x7A, 0xAA, 0x97, 0x53, 0x66, 0x12]) +SUM32_TABLE_1 = bytes([0x7A, 0xAA, 0x97, 0x53, 0x66, 0x12, 0xDE, 0xDE, 0x35]) +# BROKEN!!!: +def calc_sum32(data): + t0_i = len(data) + t1_i = data[(len(data) >> 1)+1] + out = bytearray(4) + for i in range(len(data)): + tmp = (SUM32_TABLE_1[t1_i % 9] ^ SUM32_TABLE_0[t0_i % 7]) ^ data[i] + out[i%4] = (out[i%4] + tmp) & 0xFF + + t0_i += 1 + t1_i += 1 + + + return Int32ul.parse(out) + +def read_binary8_part(stream): + # Read the header and decrypt the header first to get the size. + enc_bytes = bytearray(stream.read(12)) + dec_header_bytes = decode_binary8(enc_bytes[1:], enc_bytes[0]) + header = Binary8Header.parse(dec_header_bytes) + + # Then read the body, append to the header, and decrypt the full thing. + body_bytes = stream.read(header.body_size) + enc_bytes.extend(body_bytes) + dec_bytes = decode_binary8(enc_bytes[1:], enc_bytes[0]) + + reenc_bytes = encode_binary8(dec_bytes, enc_bytes[0]) + + import zlib + print("Good: {}".format(zlib.crc32(enc_bytes[1:]) == zlib.crc32(reenc_bytes))) + print("calc_sum32: {:X}".format(calc_sum32(dec_bytes[11:]))) + print("header checksum: {:X}".format(header.checksum)) + + # Then return the parsed header and just the raw body data. + return (header, dec_bytes[11:]) + +""" +with open('tw_server_list_resp.bin', 'rb') as f: + (header, data) = read_binary8_part(f) + from hexdump import hexdump + hexdump(data[:16]) + print(len(data)) +""" + +with open('dec_bin8_data_dump.bin', 'rb') as f: + print("calc_sum32: {:X}".format(calc_sum32(f.read()))) + print("want: 74EF4928") \ No newline at end of file