Initial commit

This commit is contained in:
Andrew Gutekanst
2019-12-19 21:53:28 +09:00
parent fc5b1ac3b5
commit 96ec589651
12 changed files with 566 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 The Erupe Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Erupe
Nothing here yet :)
Based on the TW version. Requires a local mirror of the original launcher site to be placed in `./www/g6_launcher` until I can RE the launcher and figure out which JS callbacks it requires.

9
go.mod Normal file
View File

@@ -0,0 +1,9 @@
module github.com/Andoryuuta/Erupe
go 1.13
require (
github.com/Andoryuuta/binio v0.0.0-20160731013325-2c89946fb8c3
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0
github.com/julienschmidt/httprouter v1.3.0
)

6
go.sum Normal file
View File

@@ -0,0 +1,6 @@
github.com/Andoryuuta/binio v0.0.0-20160731013325-2c89946fb8c3 h1:N8pCiqpJAHyOO8lx/F7HzyQ/qjwatmmW0zHTt0e/SeU=
github.com/Andoryuuta/binio v0.0.0-20160731013325-2c89946fb8c3/go.mod h1:4WK1jUpH8NFdDiv7IJcBfyCIOMqKjZ15kcw5eBKALvc=
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0 h1:2pVgen9rh18IxSWxOa80bObcpyfrS6d5bJtZeCUN7rY=
github.com/Andoryuuta/byteframe v0.0.0-20191219124302-41f4085eb4c0/go.mod h1:koVyx+gN3TfE70rpOidywETVODk87304YpwW69Y27J4=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=

25
launcher_server.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func g6Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.ServeFile(w, r, "www/g6_launcher/index.html")
}
// serveLauncherHTML is responsible for serving the launcher HTML and (HACK) serverlist.xml.
func serveLauncherHTML(listenAddr string) {
// Manually route the folder root to index.html? Is there a better way to do this?
router := httprouter.New()
router.GET("/g6_launcher/", g6Index)
static := httprouter.New()
static.ServeFiles("/*filepath", http.Dir("www"))
router.NotFound = static
http.ListenAndServe(listenAddr, router)
}

20
main.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"fmt"
"io"
_ "time"
"github.com/Andoryuuta/Erupe/network"
)
func main() {
fmt.Println("Starting!")
go serveLauncherHTML(":80")
go doSignServer(":53312")
for {
time.Sleep(1 * time.Second)
}
}

78
network/crypt_conn.go Normal file
View File

@@ -0,0 +1,78 @@
package network
import (
"errors"
"fmt"
"io"
"net"
"github.com/Andoryuuta/Erupe/network/crypto"
)
// CryptConn represents a MHF encrypted two-way connection,
// it automatically handles encryption, decryption, and key rotation via it's methods.
type CryptConn struct {
conn net.Conn
readKeyRot uint32
sendKeyRot uint32
sentPackets int32
prevSendPacketCombinedCheck uint16
}
// NewCryptConn creates a new CryptConn with proper default values.
func NewCryptConn(conn net.Conn) *CryptConn {
cc := &CryptConn{
conn: conn,
readKeyRot: 995117,
sendKeyRot: 995117,
sentPackets: 0,
prevSendPacketCombinedCheck: 0,
}
return cc
}
// ReadPacket reads an packet from the connection and returns the decrypted data.
func (cc *CryptConn) ReadPacket() ([]byte, error) {
// Read the raw 14 byte header.
headerData := make([]byte, CryptPacketHeaderLength)
_, err := io.ReadFull(cc.conn, headerData)
if err != nil {
return nil, err
}
//fmt.Printf("Header: %s\n", hex.Dump(headerData))
// Parse the data into a usable struct.
cph, err := NewCryptPacketHeader(headerData)
if err != nil {
return nil, err
}
// Now read the encrypted packet body after getting its size from the header.
encryptedPacketBody := make([]byte, cph.DataSize)
_, err = io.ReadFull(cc.conn, encryptedPacketBody)
if err != nil {
return nil, err
}
// Update the key rotation before decrypting.
if cph.KeyRotDelta != 0 {
cc.readKeyRot = (uint32(cph.KeyRotDelta) * (cc.readKeyRot + 1))
}
out, combinedCheck, check0, check1, check2 := crypto.Decrypt(encryptedPacketBody, cc.readKeyRot, nil)
if cph.Check0 != check0 || cph.Check1 != check1 || cph.Check2 != check2 {
fmt.Printf("got c0 %X, c1 %X, c2 %X\n", check0, check1, check2)
fmt.Printf("want c0 %X, c1 %X, c2 %X\n", cph.Check0, cph.Check1, cph.Check2)
return nil, errors.New("decrypted data checksum doesn't match header")
}
_ = 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
}

67
network/crypt_packet.go Normal file
View File

@@ -0,0 +1,67 @@
package network
import (
"bytes"
"encoding/binary"
)
const (
// CryptPacketHeaderLength represents the byte-length of
// an encrypted packet header.
CryptPacketHeaderLength = 14
)
// CryptPacketHeader represents the parsed information of an encrypted packet header.
type CryptPacketHeader struct {
Pf0 byte
KeyRotDelta byte
PacketNum uint16
DataSize uint16
PrevPacketCombinedCheck uint16
Check0 uint16
Check1 uint16
Check2 uint16
}
// NewCryptPacketHeader parses raw bytes into a CryptPacketHeader
func NewCryptPacketHeader(data []byte) (*CryptPacketHeader, error) {
var c = CryptPacketHeader{}
r := bytes.NewReader(data)
var err error
err = binary.Read(r, binary.BigEndian, &c.Pf0)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.KeyRotDelta)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.PacketNum)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.DataSize)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.PrevPacketCombinedCheck)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.Check0)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.Check1)
if err != nil {
return nil, err
}
err = binary.Read(r, binary.BigEndian, &c.Check2)
if err != nil {
return nil, err
}
return &c, nil
}

84
network/crypto/crypto.go Normal file
View File

@@ -0,0 +1,84 @@
package crypto
var (
_encryptKey = []byte{0x90, 0x51, 0x26, 0x25, 0x04, 0xBF, 0xCF, 0x4C, 0x92, 0x02, 0x52, 0x7A, 0x70, 0x1A, 0x41, 0x88, 0x8C, 0xC2, 0xCE, 0xB8, 0xF6, 0x57, 0x7E, 0xBA, 0x83, 0x63, 0x2C, 0x24, 0x9A, 0x67, 0x86, 0x0C, 0xBE, 0x72, 0xFD, 0xB6, 0x7B, 0x79, 0xB0, 0x22, 0x5A, 0x60, 0x5C, 0x4F, 0x49, 0xE2, 0x0E, 0xF5, 0x3A, 0x81, 0xAE, 0x11, 0x6B, 0xF0, 0xA1, 0x01, 0xE8, 0x65, 0x8D, 0x5B, 0xDC, 0xCC, 0x93, 0x18, 0xB3, 0xAB, 0x77, 0xF7, 0x8E, 0xEC, 0xEF, 0x05, 0x00, 0xCA, 0x4E, 0xA7, 0xBC, 0xB5, 0x10, 0xC6, 0x6C, 0xC0, 0xC4, 0xE5, 0x87, 0x3F, 0xC1, 0x82, 0x29, 0x96, 0x45, 0x73, 0x07, 0xCB, 0x43, 0xF9, 0xF3, 0x08, 0x89, 0xD0, 0x99, 0x6A, 0x3B, 0x37, 0x19, 0xD4, 0x40, 0xEA, 0xD7, 0x85, 0x16, 0x66, 0x1E, 0x9C, 0x39, 0xBB, 0xEE, 0x4A, 0x03, 0x8A, 0x36, 0x2D, 0x13, 0x1D, 0x56, 0x48, 0xC7, 0x0D, 0x59, 0xB2, 0x44, 0xA3, 0xFE, 0x8B, 0x32, 0x1B, 0x84, 0xA0, 0x2E, 0x62, 0x17, 0x42, 0xB9, 0x9B, 0x2B, 0x75, 0xD8, 0x1C, 0x3C, 0x4D, 0x76, 0x27, 0x6E, 0x28, 0xD3, 0x33, 0xC3, 0x21, 0xAF, 0x34, 0x23, 0xDD, 0x68, 0x9F, 0xF1, 0xAD, 0xE1, 0xB4, 0xE7, 0xA6, 0x74, 0x15, 0x4B, 0xFA, 0x3D, 0x5F, 0x7C, 0xDA, 0x2F, 0x0A, 0xE3, 0x7D, 0xC8, 0xB7, 0x12, 0x6F, 0x9E, 0xA9, 0x14, 0x53, 0x97, 0x8F, 0x64, 0xF4, 0xF8, 0xA2, 0xA4, 0x2A, 0xD2, 0x47, 0x9D, 0x71, 0xC5, 0xE9, 0x06, 0x98, 0x20, 0x54, 0x80, 0xAA, 0xF2, 0xAC, 0x50, 0xD6, 0x7F, 0xD9, 0xC9, 0xCD, 0x69, 0x46, 0x6D, 0x30, 0xB1, 0x58, 0x0B, 0x55, 0xD1, 0x5D, 0xD5, 0xBD, 0x31, 0xDE, 0xA5, 0xE4, 0x91, 0x0F, 0x61, 0x38, 0xDF, 0xA8, 0xE6, 0x3E, 0x1F, 0x35, 0xED, 0xDB, 0x94, 0xEB, 0x09, 0x5E, 0x95, 0xFB, 0xFC, 0xE0, 0x78, 0xFF}
_decryptKey = []byte{0x48, 0x37, 0x09, 0x76, 0x04, 0x47, 0xCC, 0x5C, 0x61, 0xF8, 0xB3, 0xE0, 0x1F, 0x7F, 0x2E, 0xEB, 0x4E, 0x33, 0xB8, 0x7A, 0xBC, 0xAB, 0x6E, 0x8C, 0x3F, 0x68, 0x0D, 0x87, 0x93, 0x7B, 0x70, 0xF2, 0xCE, 0x9D, 0x27, 0xA0, 0x1B, 0x03, 0x02, 0x97, 0x99, 0x58, 0xC5, 0x90, 0x1A, 0x79, 0x8A, 0xB2, 0xDD, 0xE6, 0x86, 0x9B, 0x9F, 0xF3, 0x78, 0x67, 0xED, 0x72, 0x30, 0x66, 0x94, 0xAE, 0xF1, 0x55, 0x6A, 0x0E, 0x8D, 0x5E, 0x82, 0x5A, 0xDB, 0xC7, 0x7D, 0x2C, 0x75, 0xAC, 0x07, 0x95, 0x4A, 0x2B, 0xD4, 0x01, 0x0A, 0xBD, 0xCF, 0xE1, 0x7C, 0x15, 0xDF, 0x80, 0x28, 0x3B, 0x2A, 0xE3, 0xF9, 0xAF, 0x29, 0xEC, 0x8B, 0x19, 0xC0, 0x39, 0x6F, 0x1D, 0xA2, 0xDA, 0x65, 0x34, 0x50, 0xDC, 0x98, 0xB9, 0x0C, 0xC9, 0x21, 0x5B, 0xAA, 0x91, 0x96, 0x42, 0xFE, 0x25, 0x0B, 0x24, 0xB0, 0xB5, 0x16, 0xD6, 0xD0, 0x31, 0x57, 0x18, 0x88, 0x6D, 0x1E, 0x54, 0x0F, 0x62, 0x77, 0x85, 0x10, 0x3A, 0x44, 0xBF, 0x00, 0xEA, 0x08, 0x3E, 0xF6, 0xFA, 0x59, 0xBE, 0xCD, 0x64, 0x1C, 0x8F, 0x71, 0xC8, 0xBA, 0xA3, 0x89, 0x36, 0xC3, 0x83, 0xC4, 0xE8, 0xA9, 0x4B, 0xEF, 0xBB, 0xD1, 0x41, 0xD3, 0xA5, 0x32, 0x9E, 0x26, 0xDE, 0x81, 0x40, 0xA7, 0x4D, 0x23, 0xB7, 0x13, 0x8E, 0x17, 0x73, 0x4C, 0xE5, 0x20, 0x05, 0x51, 0x56, 0x11, 0x9C, 0x52, 0xCA, 0x4F, 0x7E, 0xB6, 0xD8, 0x49, 0x5D, 0x3D, 0xD9, 0x12, 0x06, 0x63, 0xE2, 0xC6, 0x9A, 0x69, 0xE4, 0xD5, 0x6C, 0x92, 0xD7, 0xB1, 0xF5, 0x3C, 0xA1, 0xE7, 0xEE, 0xFD, 0xA6, 0x2D, 0xB4, 0xE9, 0x53, 0xF0, 0xA8, 0x38, 0xCB, 0x6B, 0xF7, 0x45, 0xF4, 0x74, 0x46, 0x35, 0xA4, 0xD2, 0x60, 0xC1, 0x2F, 0x14, 0x43, 0xC2, 0x5F, 0xAD, 0xFB, 0xFC, 0x22, 0x84, 0xFF}
_sharedCryptKey = []byte{0xDD, 0xA8, 0x5F, 0x1E, 0x57, 0xAF, 0xC0, 0xCC, 0x43, 0x35, 0x8F, 0xBB, 0x6F, 0xE6, 0xA1, 0xD6, 0x60, 0xB9, 0x1A, 0xAE, 0x20, 0x49, 0x24, 0x81, 0x21, 0xFE, 0x86, 0x2B, 0x98, 0xB7, 0xB3, 0xD2, 0x91, 0x01, 0x3A, 0x4C, 0x65, 0x92, 0x1C, 0xF4, 0xBE, 0xDD, 0xD9, 0x08, 0xE6, 0x81, 0x98, 0x1B, 0x8D, 0x60, 0xF3, 0x6F, 0xA1, 0x47, 0x24, 0xF1, 0x53, 0x45, 0xC8, 0x7B, 0x88, 0x80, 0x4E, 0x36, 0xC3, 0x0D, 0xC9, 0xD6, 0x8B, 0x08, 0x19, 0x0B, 0xA5, 0xC1, 0x11, 0x4C, 0x60, 0xF8, 0x5D, 0xFC, 0x15, 0x68, 0x7E, 0x32, 0xC0, 0x50, 0xAB, 0x64, 0x1F, 0x8A, 0xD4, 0x08, 0x39, 0x7F, 0xC2, 0xFB, 0xBA, 0x6C, 0xF0, 0xE6, 0xB0, 0x31, 0x10, 0xC1, 0xBF, 0x75, 0x43, 0xBB, 0x18, 0x04, 0x0D, 0xD1, 0x97, 0xF7, 0x23, 0x21, 0x83, 0x8B, 0xCA, 0x25, 0x2B, 0xA3, 0x03, 0x13, 0xEA, 0xAE, 0xFE, 0xF0, 0xEB, 0xFD, 0x85, 0x57, 0x53, 0x65, 0x41, 0x2A, 0x40, 0x99, 0xC0, 0x94, 0x65, 0x7E, 0x7C, 0x93, 0x82, 0xB0, 0xB3, 0xE5, 0xC0, 0x21, 0x09, 0x84, 0xD5, 0xEF, 0x9F, 0xD1, 0x7E, 0xDC, 0x4D, 0xF5, 0x7E, 0xCD, 0x45, 0x3C, 0x7F, 0xF5, 0x59, 0x98, 0xC6, 0x55, 0xFC, 0x9F, 0xA3, 0xB7, 0x74, 0xEE, 0x31, 0x98, 0xE6, 0xB7, 0xBE, 0x26, 0xF4, 0x3C, 0x76, 0xF1, 0x23, 0x7E, 0x02, 0x4E, 0x3C, 0xD1, 0xC7, 0x28, 0x23, 0x73, 0xC4, 0xD9, 0x5E, 0x0D, 0xA1, 0x80, 0xA5, 0xAA, 0x26, 0x0A, 0xA3, 0x44, 0x82, 0x74, 0xE6, 0x3C, 0x44, 0x27, 0x51, 0x0D, 0x5F, 0xC7, 0x9C, 0xD6, 0x63, 0x67, 0xA5, 0x27, 0x97, 0x38, 0xFB, 0x2D, 0xD3, 0xD6, 0x60, 0x25, 0x83, 0x4D, 0x37, 0x5B, 0x40, 0x59, 0x11, 0x77, 0x51, 0x11, 0x14, 0x18, 0x07, 0x63, 0xB1, 0x34, 0x3D, 0xB8, 0x60, 0x13, 0xC2, 0xE8, 0x13, 0x82}
)
// Encrypt encrypts the given data using MHF's custom encryption+checksum method.
// if a overrideByteKey value is supplied (!= nil), it will be used to override the derived/truncated key byte.
func Encrypt(data []byte, key uint32, overrideByteKey *byte) (outputData []byte, combinedCheck uint16, check0 uint16, check1 uint16, check2 uint16) {
return _generalCrypt(data, key, 0, overrideByteKey)
}
// Decrypt decrypts the given data using MHF's custom decryption+checksum method.
// if a overrideByteKey value is supplied (!= nil), it will be used to override the derived/truncated key byte.
func Decrypt(data []byte, key uint32, overrideByteKey *byte) (outputData []byte, combinedCheck uint16, check0 uint16, check1 uint16, check2 uint16) {
return _generalCrypt(data, key, 1, overrideByteKey)
}
// _generalCrypt is a generalized MHF crypto function that can perform both encryption and decryption,
// these two crypto operations are combined into a single function because they shared most of their logic.
// encrypt: cryptType==0
// decrypt: cryptType==1
func _generalCrypt(data []byte, rotKey uint32, cryptType int, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) {
cryptKeyTruncByte := byte(((rotKey >> 1) % 999983) & 0xFF)
if overrideByteKey != nil {
cryptKeyTruncByte = *overrideByteKey
}
derivedCryptKey := int32((uint32(len(data)) * uint32(cryptKeyTruncByte+1)) & 0xFFFFFFFF)
sharedBufIdx := byte(1)
accumulator0 := uint32(0)
accumulator1 := uint32(0)
accumulator2 := uint32(0)
var outputData []byte
if cryptType == 0 {
for i := 0; i < len(data); i++ {
// Do the encryption for this iteration
encKeyIdx := int32(((uint32(derivedCryptKey) >> 10) ^ uint32(data[i])) & 0xFF)
derivedCryptKey = (0x4FD * (derivedCryptKey + 1))
encKeyByte := _encryptKey[encKeyIdx]
// Update the checksum accumulators.
accumulator2 = uint32((accumulator2 + (uint32(sharedBufIdx) * uint32(data[i]))) & 0xFFFFFFFF)
accumulator1 = uint32((accumulator1 + uint32(encKeyIdx)) & 0xFFFFFFFF)
accumulator0 = uint32((accumulator0 + (uint32(encKeyByte)<<(i&7))&0xFFFFFFFF) & 0xFFFFFFFF)
// Append the output.
outputData = append(outputData, _sharedCryptKey[sharedBufIdx]^encKeyByte)
// Update the sharedBufIdx for the next iteration.
sharedBufIdx = data[i]
}
} else if cryptType == 1 {
for i := 0; i < len(data); i++ {
// Do the decryption for this iteration
oldSharedBufIdx := sharedBufIdx
tIdx := data[i] ^ _sharedCryptKey[sharedBufIdx]
decKeyByte := _decryptKey[tIdx]
sharedBufIdx = byte(((uint32(derivedCryptKey) >> 10) ^ uint32(decKeyByte)) & 0xFF)
// Update the checksum accumulators.
accumulator0 = (accumulator0 + ((uint32(tIdx) << (i & 7)) & 0xFFFFFFFF))
accumulator1 = (accumulator1 + uint32(decKeyByte)) & 0xFFFFFFFF
accumulator2 = (accumulator2 + ((uint32(oldSharedBufIdx) * uint32(sharedBufIdx)) & 0xFFFFFFFF)) & 0xFFFFFFFF
// Append the output.
outputData = append(outputData, sharedBufIdx)
// Update the key pos for next iteration.
derivedCryptKey = (0x4FD * (derivedCryptKey + 1))
}
}
combinedCheck := uint16((accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2)) & 0xFFFF)
check0 := uint16((accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16)) & 0xFFFF)
check1 := uint16((accumulator1 ^ ((accumulator1 & 0xFFFF0000) >> 16)) & 0xFFFF)
check2 := uint16((accumulator2 ^ ((accumulator2 & 0xFFFF0000) >> 16)) & 0xFFFF)
return outputData, combinedCheck, check0, check1, check2
}

View File

@@ -0,0 +1,98 @@
package crypto
import (
"bytes"
"encoding/hex"
"fmt"
"testing"
)
var commonTestData = []byte{0x74, 0x65, 0x73, 0x74}
var tests = []struct {
decryptedData []byte
key uint32
encryptedData []byte
ecc, ec0, ec1, ec2 uint16
}{
{
commonTestData,
0,
[]byte{0x46, 0x53, 0x28, 0x5E},
0x2976, 0x06ea, 0x0215, 0x08FB3,
},
{
commonTestData,
3,
[]byte{0x46, 0x95, 0x88, 0xEA},
0x2AE4, 0x0A56, 0x01CD, 0x08FB3,
},
/*
// TODO(Andoryuuta): This case fails. Debug the client and figure out if this is valid expected data.
{
commonTestData,
995117,
[]byte{0x46, 0x28, 0xFF, 0xAA},
0x2A22, 0x09D4, 0x014C, 0x08FB3,
},
*/
{
commonTestData,
0x7FFFFFFF,
[]byte{0x46, 0x53, 0x28, 0x5E},
0x2976, 0x06ea, 0x0215, 0x08FB3,
},
{
commonTestData,
0x80000000,
[]byte{0x46, 0x95, 0x88, 0xEA},
0x2AE4, 0x0A56, 0x01CD, 0x08FB3,
},
{
commonTestData,
0xFFFFFFFF,
[]byte{0x46, 0xB5, 0xDC, 0xB2},
0x2ADD, 0x09A6, 0x021E, 0x08FB3,
},
}
func TestEncrypt(t *testing.T) {
for k, tt := range tests {
testname := fmt.Sprintf("encrypt_test_%d", k)
t.Run(testname, func(t *testing.T) {
out, cc, c0, c1, c2 := Encrypt(tt.decryptedData, tt.key, nil)
if cc != tt.ecc {
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
} else if c0 != tt.ec0 {
t.Errorf("got c0 0x%X, want 0x%X", c0, tt.ec0)
} else if c1 != tt.ec1 {
t.Errorf("got c1 0x%X, want 0x%X", c1, tt.ec1)
} else if c2 != tt.ec2 {
t.Errorf("got c2 0x%X, want 0x%X", c2, tt.ec2)
} else if !bytes.Equal(out, tt.encryptedData) {
t.Errorf("got out\n\t%s\nwant\n\t%s", hex.Dump(out), hex.Dump(tt.encryptedData))
}
})
}
}
func TestDecrypt(t *testing.T) {
for k, tt := range tests {
testname := fmt.Sprintf("decrypt_test_%d", k)
t.Run(testname, func(t *testing.T) {
out, cc, c0, c1, c2 := Decrypt(tt.encryptedData, tt.key, nil)
if cc != tt.ecc {
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
} else if c0 != tt.ec0 {
t.Errorf("got c0 0x%X, want 0x%X", c0, tt.ec0)
} else if c1 != tt.ec1 {
t.Errorf("got c1 0x%X, want 0x%X", c1, tt.ec1)
} else if c2 != tt.ec2 {
t.Errorf("got c2 0x%X, want 0x%X", c2, tt.ec2)
} else if !bytes.Equal(out, tt.decryptedData) {
t.Errorf("got out\n\t%s\nwant\n\t%s", hex.Dump(out), hex.Dump(tt.decryptedData))
}
})
}
}

56
sign_server.go Normal file
View File

@@ -0,0 +1,56 @@
package main
import (
"fmt"
"io"
"net"
"github.com/Andoryuuta/Erupe/network"
"github.com/Andoryuuta/byteframe"
)
func handleSignServerConnection(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 {
panic(err)
}
bf := byteframe.NewByteFrameFromBytes(pkt)
loginType := string(bf.ReadNullTerminatedBytes())
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:\n%s", hex.Dump(pkt))
}
}
func doSignServer(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 handleSignServerConnection(conn)
}
}

97
test.py Normal file
View File

@@ -0,0 +1,97 @@
from hexdump import hexdump
import sys
ENCRYPT_KEY = b'\x90\x51\x26\x25\x04\xBF\xCF\x4C\x92\x02\x52\x7A\x70\x1A\x41\x88\x8C\xC2\xCE\xB8\xF6\x57\x7E\xBA\x83\x63\x2C\x24\x9A\x67\x86\x0C\xBE\x72\xFD\xB6\x7B\x79\xB0\x22\x5A\x60\x5C\x4F\x49\xE2\x0E\xF5\x3A\x81\xAE\x11\x6B\xF0\xA1\x01\xE8\x65\x8D\x5B\xDC\xCC\x93\x18\xB3\xAB\x77\xF7\x8E\xEC\xEF\x05\x00\xCA\x4E\xA7\xBC\xB5\x10\xC6\x6C\xC0\xC4\xE5\x87\x3F\xC1\x82\x29\x96\x45\x73\x07\xCB\x43\xF9\xF3\x08\x89\xD0\x99\x6A\x3B\x37\x19\xD4\x40\xEA\xD7\x85\x16\x66\x1E\x9C\x39\xBB\xEE\x4A\x03\x8A\x36\x2D\x13\x1D\x56\x48\xC7\x0D\x59\xB2\x44\xA3\xFE\x8B\x32\x1B\x84\xA0\x2E\x62\x17\x42\xB9\x9B\x2B\x75\xD8\x1C\x3C\x4D\x76\x27\x6E\x28\xD3\x33\xC3\x21\xAF\x34\x23\xDD\x68\x9F\xF1\xAD\xE1\xB4\xE7\xA6\x74\x15\x4B\xFA\x3D\x5F\x7C\xDA\x2F\x0A\xE3\x7D\xC8\xB7\x12\x6F\x9E\xA9\x14\x53\x97\x8F\x64\xF4\xF8\xA2\xA4\x2A\xD2\x47\x9D\x71\xC5\xE9\x06\x98\x20\x54\x80\xAA\xF2\xAC\x50\xD6\x7F\xD9\xC9\xCD\x69\x46\x6D\x30\xB1\x58\x0B\x55\xD1\x5D\xD5\xBD\x31\xDE\xA5\xE4\x91\x0F\x61\x38\xDF\xA8\xE6\x3E\x1F\x35\xED\xDB\x94\xEB\x09\x5E\x95\xFB\xFC\xE0\x78\xFF'
DECRYPT_KEY = b'\x48\x37\x09\x76\x04\x47\xCC\x5C\x61\xF8\xB3\xE0\x1F\x7F\x2E\xEB\x4E\x33\xB8\x7A\xBC\xAB\x6E\x8C\x3F\x68\x0D\x87\x93\x7B\x70\xF2\xCE\x9D\x27\xA0\x1B\x03\x02\x97\x99\x58\xC5\x90\x1A\x79\x8A\xB2\xDD\xE6\x86\x9B\x9F\xF3\x78\x67\xED\x72\x30\x66\x94\xAE\xF1\x55\x6A\x0E\x8D\x5E\x82\x5A\xDB\xC7\x7D\x2C\x75\xAC\x07\x95\x4A\x2B\xD4\x01\x0A\xBD\xCF\xE1\x7C\x15\xDF\x80\x28\x3B\x2A\xE3\xF9\xAF\x29\xEC\x8B\x19\xC0\x39\x6F\x1D\xA2\xDA\x65\x34\x50\xDC\x98\xB9\x0C\xC9\x21\x5B\xAA\x91\x96\x42\xFE\x25\x0B\x24\xB0\xB5\x16\xD6\xD0\x31\x57\x18\x88\x6D\x1E\x54\x0F\x62\x77\x85\x10\x3A\x44\xBF\x00\xEA\x08\x3E\xF6\xFA\x59\xBE\xCD\x64\x1C\x8F\x71\xC8\xBA\xA3\x89\x36\xC3\x83\xC4\xE8\xA9\x4B\xEF\xBB\xD1\x41\xD3\xA5\x32\x9E\x26\xDE\x81\x40\xA7\x4D\x23\xB7\x13\x8E\x17\x73\x4C\xE5\x20\x05\x51\x56\x11\x9C\x52\xCA\x4F\x7E\xB6\xD8\x49\x5D\x3D\xD9\x12\x06\x63\xE2\xC6\x9A\x69\xE4\xD5\x6C\x92\xD7\xB1\xF5\x3C\xA1\xE7\xEE\xFD\xA6\x2D\xB4\xE9\x53\xF0\xA8\x38\xCB\x6B\xF7\x45\xF4\x74\x46\x35\xA4\xD2\x60\xC1\x2F\x14\x43\xC2\x5F\xAD\xFB\xFC\x22\x84\xFF'
SHARED_CRYPT_KEY = b'\xDD\xA8\x5F\x1E\x57\xAF\xC0\xCC\x43\x35\x8F\xBB\x6F\xE6\xA1\xD6\x60\xB9\x1A\xAE\x20\x49\x24\x81\x21\xFE\x86\x2B\x98\xB7\xB3\xD2\x91\x01\x3A\x4C\x65\x92\x1C\xF4\xBE\xDD\xD9\x08\xE6\x81\x98\x1B\x8D\x60\xF3\x6F\xA1\x47\x24\xF1\x53\x45\xC8\x7B\x88\x80\x4E\x36\xC3\x0D\xC9\xD6\x8B\x08\x19\x0B\xA5\xC1\x11\x4C\x60\xF8\x5D\xFC\x15\x68\x7E\x32\xC0\x50\xAB\x64\x1F\x8A\xD4\x08\x39\x7F\xC2\xFB\xBA\x6C\xF0\xE6\xB0\x31\x10\xC1\xBF\x75\x43\xBB\x18\x04\x0D\xD1\x97\xF7\x23\x21\x83\x8B\xCA\x25\x2B\xA3\x03\x13\xEA\xAE\xFE\xF0\xEB\xFD\x85\x57\x53\x65\x41\x2A\x40\x99\xC0\x94\x65\x7E\x7C\x93\x82\xB0\xB3\xE5\xC0\x21\x09\x84\xD5\xEF\x9F\xD1\x7E\xDC\x4D\xF5\x7E\xCD\x45\x3C\x7F\xF5\x59\x98\xC6\x55\xFC\x9F\xA3\xB7\x74\xEE\x31\x98\xE6\xB7\xBE\x26\xF4\x3C\x76\xF1\x23\x7E\x02\x4E\x3C\xD1\xC7\x28\x23\x73\xC4\xD9\x5E\x0D\xA1\x80\xA5\xAA\x26\x0A\xA3\x44\x82\x74\xE6\x3C\x44\x27\x51\x0D\x5F\xC7\x9C\xD6\x63\x67\xA5\x27\x97\x38\xFB\x2D\xD3\xD6\x60\x25\x83\x4D\x37\x5B\x40\x59\x11\x77\x51\x11\x14\x18\x07\x63\xB1\x34\x3D\xB8\x60\x13\xC2\xE8\x13\x82'
class PacketCrypto(object):
def encrypt(data, rot_key, override_byte_key=None):
return PacketCrypto._general_crypt(data, rot_key, 0, override_byte_key)
def decrypt(data, rot_key, override_byte_key=None):
return PacketCrypto._general_crypt(data, rot_key, 1, override_byte_key)
def _general_crypt(data, rot_key, crypt_type, override_byte_key=None):
"""A generic crypto function for both encryption and decryption
:param data: input data
:param rot_key: crypto key index rotation
:param crypt_type: determines whether to encrypt(0) or decrypt(1)
:param override_byte_key: override value for the truncated rotation index byte
:type data: bytes
:type rot_key: int
:type crypt_type: int
:type override_byte_key: bool
"""
unk_cryptkey_rot_arg = ((rot_key >> 1) % 999983) & 0xFF
print("unk_cryptkey_rot_arg: {:X}".format(unk_cryptkey_rot_arg))
if override_byte_key is not None:
unk_cryptkey_rot_arg = override_byte_key
unk_derived_cryptkey_rot = (len(data) * (unk_cryptkey_rot_arg+1)) & 0xFFFFFFFF
shared_buf_idx = 1
accumulator_0 = 0
accumulator_1 = 0
accumulator_2 = 0
print('unk_derived_cryptkey_rot: {:X}'.format(unk_derived_cryptkey_rot))
output_data = bytearray()
if crypt_type == 0: # Encrypt
for i in range(len(data)):
print("\n")
# Do the encryption for this iteration
enc_key_idx = ((unk_derived_cryptkey_rot >> 10) ^ data[i]) & 0xFF
print('enc_key_idx: {:X}'.format(enc_key_idx))
unk_derived_cryptkey_rot = (0x4FD * (unk_derived_cryptkey_rot + 1)) & 0xFFFFFFFF
print('unk_derived_cryptkey_rot: {:X}'.format(unk_derived_cryptkey_rot))
enc_key_byte = ENCRYPT_KEY[enc_key_idx]
print('enc_key_byte: {:X}'.format(enc_key_byte))
# Update the checksum accumulators.
accumulator_2 = (accumulator_2 + (shared_buf_idx * data[i])) & 0xFFFFFFFF
accumulator_1 = (accumulator_1 + enc_key_idx) & 0xFFFFFFFF
accumulator_0 = (accumulator_0 + (enc_key_byte << (i & 7)) & 0xFFFFFFFF) & 0xFFFFFFFF
# Append the output.
output_data.append(SHARED_CRYPT_KEY[shared_buf_idx] ^ enc_key_byte)
# Update the shared_buf_idx for the next iteration.
shared_buf_idx = data[i]
elif crypt_type == 1: # Decrypt
for i in range(len(data)):
# Do the decryption for this iteration
old_shared_buf_idx = shared_buf_idx
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
# Update the checksum accumulators.
accumulator_0 = (accumulator_0 + ((t_idx << (i & 7)) & 0xFFFFFFFF))
accumulator_1 = (accumulator_1 + dec_key_byte) & 0xFFFFFFFF
accumulator_2 = (accumulator_2 + ((old_shared_buf_idx * shared_buf_idx)&0xFFFFFFFF)) & 0xFFFFFFFF
# Append the output.
output_data.append(shared_buf_idx)
# Update the key pos for next iteration.
unk_derived_cryptkey_rot = (0x4FD * (unk_derived_cryptkey_rot + 1)) & 0xFFFFFFFF
else:
raise Exception("Unknown crypt_type value.")
combined_check = (accumulator_1 + (accumulator_0 >> 1) + (accumulator_2 >> 2)) & 0xFFFF
check_0 = (accumulator_0 ^ ((accumulator_0&0xFFFF0000)>>16)) & 0xFFFF
check_1 = (accumulator_1 ^ ((accumulator_1&0xFFFF0000)>>16)) & 0xFFFF
check_2 = (accumulator_2 ^ ((accumulator_2&0xFFFF0000)>>16)) & 0xFFFF
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))