From b2bd8a18c427ef4b2f8d5874ecdfc9ba60254331 Mon Sep 17 00:00:00 2001 From: Night12138 <43197204+Night12138@users.noreply.github.com> Date: Sun, 13 Nov 2022 13:09:39 +0800 Subject: [PATCH] feat: add bypass mode; fix: add new keys (#3) --- gover/README.md | 11 ++ gover/go.mod | 2 + gover/go.sum | 15 ++ gover/kcp/kcp_test.go | 2 +- gover/main.go | 85 ++++++++--- gover/proxy/bypass.go | 238 +++++++++++++++++++++++++++++ gover/proxy/bypass/bypass.go | 9 ++ gover/proxy/bypass/bypass_empty.go | 14 ++ gover/proxy/bypass/bypass_pcap.go | 160 +++++++++++++++++++ gover/proxy/bypass/bypass_test.go | 11 ++ gover/proxy/handlers.go | 58 +++++-- gover/proxy/socket.go | 42 +++-- gover/utils/crypto.go | 30 +++- gover/utils/prng.go | 101 ++++++++++++ gover/utils/prng_test.go | 15 ++ gover/utils/recorder.go | 4 +- keys/MHYPrivCN1.pem | 27 ++++ keys/MHYPrivOS1.pem | 27 ++++ keys/MHYSignCN1.pem | 9 ++ keys/MHYSignOS1.pem | 9 ++ 20 files changed, 810 insertions(+), 59 deletions(-) create mode 100644 gover/proxy/bypass.go create mode 100644 gover/proxy/bypass/bypass.go create mode 100644 gover/proxy/bypass/bypass_empty.go create mode 100644 gover/proxy/bypass/bypass_pcap.go create mode 100644 gover/proxy/bypass/bypass_test.go create mode 100644 gover/utils/prng.go create mode 100644 gover/utils/prng_test.go create mode 100644 keys/MHYPrivCN1.pem create mode 100644 keys/MHYPrivOS1.pem create mode 100644 keys/MHYSignCN1.pem create mode 100644 keys/MHYSignOS1.pem diff --git a/gover/README.md b/gover/README.md index 4d1b9720..7f1b2af1 100644 --- a/gover/README.md +++ b/gover/README.md @@ -25,6 +25,17 @@ go run main.go 6. Start the game and have fun! +## For your safety +MHY will frequently update their resource files, so our hardcoded checksums are not always available. For your account safety, it is recommended that you enable bypass mode. + +1. Install [npcap](https://npcap.com/) on windows or [libpcap](https://www.tcpdump.org/) on linux + +2. Run `go build -tags bypass` to build the bypass version + +3. Run `gover.exe --bypass` on windows or `.\gover --bypass` on linux to enable runtime bypass + +4. (Known issue) Make sure your game was the first startup before login, which means you shouldn't logout then re-login without completely quit the game, or will cause capture failed + ## Fiddler Script ```cs /* Gidra proxy fiddler script */ diff --git a/gover/go.mod b/gover/go.mod index e8be2d9a..20ed2db3 100644 --- a/gover/go.mod +++ b/gover/go.mod @@ -10,5 +10,7 @@ require ( require ( github.com/ThreeKing2018/gocolor v0.0.0-20190625094635-394e0e24c0d0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/gopacket v1.1.19 // indirect + golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/gover/go.sum b/gover/go.sum index 4e985562..fe9b9bb0 100644 --- a/gover/go.sum +++ b/gover/go.sum @@ -5,8 +5,23 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/yezihack/colorlog v0.0.0-20190312024641-4717a40e9990 h1:62I1TD5lofiBbO3+oS5wVXBCB7hyncP4Ge0UsVKSag0= github.com/yezihack/colorlog v0.0.0-20190312024641-4717a40e9990/go.mod h1:oeujxvLndaRV//iyjkRt4o71gSTvyf+T4NOt0ZQSWc4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gover/kcp/kcp_test.go b/gover/kcp/kcp_test.go index 29ec7d7d..0a5ae1fa 100644 --- a/gover/kcp/kcp_test.go +++ b/gover/kcp/kcp_test.go @@ -10,7 +10,7 @@ func TestKCP(t *testing.T) { fmt.Println(kcp, err) kcp.NoDelay(1, 10, 2, 1) kcp.Send([]byte("hello world")) - kcp.Update() + kcp.Update(0) kcp.Input([]byte{1, 0, 0, 0, 2, 0, 0, 0, 81, 0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}) d := make([]byte, 1500) n := kcp.Recv(d) diff --git a/gover/main.go b/gover/main.go index d5a5132c..b8e458a8 100644 --- a/gover/main.go +++ b/gover/main.go @@ -17,6 +17,7 @@ import ( "github.com/MoonlightPS/Iridium-gidra/gover/ec2b" "github.com/MoonlightPS/Iridium-gidra/gover/gen" "github.com/MoonlightPS/Iridium-gidra/gover/proxy" + "github.com/MoonlightPS/Iridium-gidra/gover/proxy/bypass" "github.com/MoonlightPS/Iridium-gidra/gover/utils" "github.com/yezihack/colorlog" "google.golang.org/protobuf/proto" @@ -26,7 +27,7 @@ const HTTP_LST = ":8081" type serverDesc struct { addr *net.UDPAddr - sock *proxy.KCPSocket + sock proxy.ProxyInterface } type regionRsp struct { @@ -35,6 +36,7 @@ type regionRsp struct { } var gameServers = map[string]*serverDesc{} +var bypassMode = false func WaitExit() { c := make(chan os.Signal, 1) @@ -89,8 +91,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { colorlog.Error("failed request dispatch, err: %+v", err) return } - - regionInfo := &gen.QueryCurrRegionHttpRsp{} + originResp := append(make([]byte, 0, len(result)), result...) ver := r.URL.Query().Get("version") v2 := strings.Contains(ver, "2.7.5") || strings.Contains(ver, "2.8.") || strings.Contains(ver, "3.") @@ -127,6 +128,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { } } + regionInfo := &gen.QueryCurrRegionHttpRsp{} err = proto.Unmarshal(result, regionInfo) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -140,7 +142,22 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { IP: net.ParseIP(regionInfo.GetRegionInfo().GetGateserverIp()), Port: int(regionInfo.GetRegionInfo().GetGateserverPort()), } - if s, ok := gameServers[target.String()]; ok { + if bypassMode { + keyBytes, err := ec2b.Derive(regionInfo.GetClientSecretKey()) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + colorlog.Error("generate key error, err: %+v", err) + return + } + + key := utils.NewPacketKey() + key.SetKey(keyBytes) + sock := proxy.NewBypassSocket(target, key, keyID) + sock.Start() + gameServers[target.String()] = &serverDesc{addr: nil, sock: sock} + colorlog.Info("starting new bypass sniffer on %+v", target) + } else if s, ok := gameServers[target.String()]; ok { regionInfo.GetRegionInfo().GateserverIp = s.addr.IP.String() regionInfo.GetRegionInfo().GateserverPort = uint32(s.addr.Port) } else { @@ -173,31 +190,38 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { } } - result, err = proto.Marshal(regionInfo) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - colorlog.Error("failed marshal region, err: %+v", err) - return - } - if v2 { - result, sign, err := utils.EncryptWithSign(result, keyID) + if bypassMode { + result = originResp + } else { + result, err = proto.Marshal(regionInfo) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) - colorlog.Error("failed enc region, err: %+v", err) + colorlog.Error("failed marshal region, err: %+v", err) return } - resp := ®ionRsp{ - Content: base64.StdEncoding.EncodeToString(result), - Sign: base64.StdEncoding.EncodeToString(sign), - } - result, err = json.Marshal(resp) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - colorlog.Error("failed marshal region json, err: %+v", err) - return + } + if v2 { + if !bypassMode { + var sign []byte + result, sign, err = utils.EncryptWithSign(result, keyID) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + colorlog.Error("failed enc region, err: %+v", err) + return + } + resp := ®ionRsp{ + Content: base64.StdEncoding.EncodeToString(result), + Sign: base64.StdEncoding.EncodeToString(sign), + } + result, err = json.Marshal(resp) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + colorlog.Error("failed marshal region json, err: %+v", err) + return + } } w.Header().Set("Content-Length", strconv.Itoa(len(result))) w.Header().Set("Content-Type", "application/json") @@ -212,6 +236,18 @@ func main() { if err != nil { panic(err) } + for _, arg := range os.Args { + if arg == "--bypass" { + bypassMode = true + colorlog.Warn("service is running at bypass mode") + } + } + if bypassMode { + err = bypass.StartBypassService() + if err != nil { + panic(err) + } + } http.HandleFunc("/query_cur_region", indexHandler) go http.ListenAndServe(HTTP_LST, nil) colorlog.Info("running...") @@ -219,4 +255,5 @@ func main() { for _, v := range gameServers { v.sock.Stop() } + bypass.StopBypassService() } diff --git a/gover/proxy/bypass.go b/gover/proxy/bypass.go new file mode 100644 index 00000000..7027f8d5 --- /dev/null +++ b/gover/proxy/bypass.go @@ -0,0 +1,238 @@ +package proxy + +import ( + "net" + "sync" + + "github.com/MoonlightPS/Iridium-gidra/gover/kcp" + "github.com/MoonlightPS/Iridium-gidra/gover/proxy/bypass" + "github.com/MoonlightPS/Iridium-gidra/gover/utils" + "github.com/yezihack/colorlog" +) + +var prng *utils.CompatPrng + +func genPrngSeed(seed uint64) uint64 { + return utils.NewCompatPrng(int32(seed)).SafeUInt64() +} + +func sniffKey(mSeed, sentMs uint64, packet []byte) uint64 { + key := utils.NewPacketKey() + buf := make([]byte, 4) + sniff := func(seed uint64) bool { + key.GenKey(seed) + copy(buf, packet) + key.Xor(buf) + if be.Uint16(buf) == 0x4567 { + return true + } + return false + } + for times := uint64(0); times < 1e4; times++ { + seed := genPrngSeed(sentMs+times) ^ mSeed + if sniff(seed) { + colorlog.Debug("key found, seed: %d", seed) + return seed + } + seed = genPrngSeed(sentMs-times) ^ mSeed + if sniff(seed) { + colorlog.Debug("key found, seed: %d", seed) + return seed + } + } + colorlog.Debug("key not found") + return 0 +} + +func (c *KCPConn) StartBypass() { + go func() { + // process client req + ch, recorder, parser, server := c.cChan, c.recorder, c.parser, c.server + for packet := range ch { + // now := time.Now() + cmd, err := parser.ParseCmd(packet) + if err != nil && c.clientSeed != 0 { + c.seed = sniffKey(c.seed, c.clientSeed, packet) + c.key.GenKey(c.seed) + c.clientSeed = 0 + cmd, err = parser.ParseCmd(packet) + } + if err != nil { + colorlog.Error("parse client packet failed! err: %+v", err) + continue + } + recorder.Record(packet, utils.SOURCE_CLIENT, cmd) + + // colorlog.Debug("client recv packet cmd:%d, n:%d", cmd, len(packet)) + + if handler, ok := handlersMap[cmd]; ok { + _, err = handler(c, packet, true) + if err != nil { + colorlog.Error("handle client packet %d failed! err: %+v", cmd, err) + continue + } + } + server.Update(kcp.CurrentMs()) + // colorlog.Debug("client packet handle take: %v", time.Since(now)) + } + colorlog.Warn("processor client quit") + }() + go func() { + // process server rsp + ch, recorder, parser, client := c.sChan, c.recorder, c.parser, c.client + for packet := range ch { + // now := time.Now() + cmd, err := parser.ParseCmd(packet) + if err != nil { + colorlog.Error("parse server packet failed! err: %+v", err) + continue + } + recorder.Record(packet, utils.SOURCE_SERVER, cmd) + + // colorlog.Debug("server recv packet cmd:%d, n:%d", cmd, len(packet)) + + if handler, ok := handlersMap[cmd]; ok { + _, err = handler(c, packet, true) + if err != nil { + colorlog.Error("handle server packet %d failed! err: %+v", cmd, err) + continue + } + } + client.Update(kcp.CurrentMs()) + // colorlog.Debug("server packet handle take: %v", time.Since(now)) + } + colorlog.Warn("processor server quit") + }() + + c.recorder.Start() +} + +func (c *KCPConn) InputServer(data []byte, size int) int { + res := c.server.Input(data[:size]) + if res != 0 { + return res + } + n := c.server.Recv(c.recv) + for n > 0 { + packet := make([]byte, n) + copy(packet, c.recv) + c.sChan <- packet + n = c.server.Recv(c.recv) + } + return 0 +} + +func ConstructBypassConn(hs *Handshake, key *utils.PacketKey, keyID int) (*KCPConn, error) { + client, err := kcp.NewKCPWithToken(hs.conv, hs.token, nil) + if err != nil { + return nil, err + } + client.SetMtu(1200) + client.WndSize(1024, 1024) + client.NoDelay(1, 10, 2, 1) + + server, err := kcp.NewKCPWithToken(hs.conv, hs.token, nil) + if err != nil { + return nil, err + } + server.SetMtu(1200) + server.WndSize(1024, 1024) + server.NoDelay(1, 10, 2, 1) + + parser := utils.NewPacketHandler() + parser.SetKey(key) + + kConn := &KCPConn{ + client: client, + cChan: make(chan []byte, 8192), + server: server, + sChan: make(chan []byte, 8192), + key: key, + parser: parser, + recorder: utils.NewRecorder(16384, parser), + hs: hs, + running: true, + keyID: keyID, + recv: make([]byte, BUFFER_SIZE), + send: make([]byte, BUFFER_SIZE), + } + + kConn.StartBypass() + + colorlog.Info("conn sniffed") + + return kConn, nil +} + +type BypassSocket struct { + remote *net.UDPAddr + key *utils.PacketKey + keyID int + conns *sync.Map +} + +func (b *BypassSocket) Start() { + conns := &sync.Map{} + b.conns = conns + bypass.RegisterCallback(&bypass.CallbackDesc{ + IP: b.remote.IP.To4(), + Port: uint16(b.remote.Port), + Func: func(buf []byte, source int) { + if IsHandshakePacket(buf) { + handshake, err := ParseHandshakePacket(buf) + if err != nil { + colorlog.Error("handle handshake failed, err: %+v", err) + return + } + switch handshake.m1 { + case HANDSHAKE_ACK: + conn, err := ConstructBypassConn(handshake, b.key.Duplicate(), b.keyID) + if err != nil { + colorlog.Error("construct kcp conn failed, err: %+v", err) + return + } + conns.Store(conn.hs.lid, conn) + case HANDSHAKE_FIN: + if i, ok := conns.LoadAndDelete(handshake.lid); ok { + i.(*KCPConn).Close(handshake) + } + } + return + } + lid := ToLID(le.Uint32(buf), le.Uint32(buf[4:])) + if i, ok := conns.Load(lid); ok { + var res int + if source == utils.SOURCE_CLIENT { + res = i.(*KCPConn).Input(buf, len(buf)) + } else { + res = i.(*KCPConn).InputServer(buf, len(buf)) + } + if res != 0 { + colorlog.Error("write kcp packet failed, lid: %d", lid) + } + } else { + colorlog.Warn("not found session with %d", lid) + } + }, + }) +} +func (b *BypassSocket) Stop() { + bypass.RemoveCallback(&bypass.CallbackDesc{ + IP: b.remote.IP.To4(), + Port: uint16(b.remote.Port), + }) + b.conns.Range(func(key, value interface{}) bool { + value.(*KCPConn).Close(nil) + return true + }) + b.conns = nil +} + +func NewBypassSocket(remote *net.UDPAddr, dispatchKey *utils.PacketKey, keyID int) ProxyInterface { + return &BypassSocket{ + remote: remote, + key: dispatchKey, + keyID: keyID, + conns: nil, + } +} diff --git a/gover/proxy/bypass/bypass.go b/gover/proxy/bypass/bypass.go new file mode 100644 index 00000000..5287035f --- /dev/null +++ b/gover/proxy/bypass/bypass.go @@ -0,0 +1,9 @@ +package bypass + +import "net" + +type CallbackDesc struct { + IP net.IP + Port uint16 + Func func(data []byte, source int) +} diff --git a/gover/proxy/bypass/bypass_empty.go b/gover/proxy/bypass/bypass_empty.go new file mode 100644 index 00000000..a425f653 --- /dev/null +++ b/gover/proxy/bypass/bypass_empty.go @@ -0,0 +1,14 @@ +//go:build !bypass +// +build !bypass + +package bypass + +import "errors" + +func CheckBypassBuild() bool { return false } +func StartBypassService() error { + return errors.New("not build bypass, please build with `-tags bypass`") +} +func RegisterCallback(cb *CallbackDesc) {} +func RemoveCallback(cb *CallbackDesc) {} +func StopBypassService() {} diff --git a/gover/proxy/bypass/bypass_pcap.go b/gover/proxy/bypass/bypass_pcap.go new file mode 100644 index 00000000..25e016ae --- /dev/null +++ b/gover/proxy/bypass/bypass_pcap.go @@ -0,0 +1,160 @@ +//go:build bypass +// +build bypass + +package bypass + +import ( + "encoding/binary" + "net" + "strings" + "sync" + + "github.com/MoonlightPS/Iridium-gidra/gover/utils" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/yezihack/colorlog" +) + +var be = binary.BigEndian + +func getAllDevicesHandlers() (handles []*pcap.Handle, err error) { + devs, err := pcap.FindAllDevs() + if err != nil { + return + } + for _, dev := range devs { + desc := strings.ToLower(dev.Description) + if strings.Contains(desc, "loopback") || + strings.Contains(desc, "virtual") || + strings.Contains(desc, "miniport") { + continue + } + handle, err := pcap.OpenLive(dev.Name, 1600, true, pcap.BlockForever) + if err != nil { + for _, h := range handles { + h.Close() + } + return nil, err + } + if handle.LinkType() != layers.LinkTypeEthernet { + handle.Close() + continue + } + handle.SetBPFFilter("udp portrange 22101-22102") + handles = append(handles, handle) + } + return +} + +type notifyObj struct { + cb *CallbackDesc + remove bool +} + +type bypassListener struct { + handle *pcap.Handle + notify chan notifyObj + quit chan int +} + +func (b *bypassListener) Start() { + go func() { + source := gopacket.NewPacketSource(b.handle, b.handle.LinkType()) + lstMap := map[uint64]func(data []byte, source int){} + for { + select { + case pk := <-source.Packets(): + var src, dst net.IP + if ipLayer := pk.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv4) + src, dst = ip.SrcIP, ip.DstIP + } + if len(src) < 4 || len(dst) < 4 { + continue + } + if udpLayer := pk.Layer(layers.LayerTypeUDP); udpLayer != nil { + udp, _ := udpLayer.(*layers.UDP) + id1 := (uint64(be.Uint32(src)) << 32) | uint64(udp.SrcPort) + id2 := (uint64(be.Uint32(dst)) << 32) | uint64(udp.DstPort) + if f, ok := lstMap[id1]; ok { + f(udp.Payload, utils.SOURCE_SERVER) + } else if f, ok := lstMap[id2]; ok { + f(udp.Payload, utils.SOURCE_CLIENT) + } + } + case notify := <-b.notify: + if len(notify.cb.IP) < 4 { + continue + } + id := (uint64(be.Uint32(notify.cb.IP)) << 32) | uint64(notify.cb.Port) + if notify.remove { + delete(lstMap, id) + } else if notify.cb.Func != nil { + lstMap[id] = notify.cb.Func + colorlog.Info("register func on %s:%d with %d", notify.cb.IP, notify.cb.Port, id) + } + case <-b.quit: + return + } + } + }() +} + +func (b *bypassListener) Stop() { + b.quit <- 1 +} + +var bypassObj = struct { + handles []*bypassListener + mu sync.Mutex +}{} + +func CheckBypassBuild() bool { return true } + +func StartBypassService() error { + bypassObj.mu.Lock() + defer bypassObj.mu.Unlock() + if len(bypassObj.handles) > 0 { + return nil + } + handles, err := getAllDevicesHandlers() + if err != nil { + return err + } + for _, h := range handles { + listener := &bypassListener{ + handle: h, + notify: make(chan notifyObj, 1024), + quit: make(chan int), + } + listener.Start() + bypassObj.handles = append(bypassObj.handles, listener) + } + return nil +} + +func RegisterCallback(cb *CallbackDesc) { + bypassObj.mu.Lock() + defer bypassObj.mu.Unlock() + for _, h := range bypassObj.handles { + h.notify <- notifyObj{cb: cb} + } +} + +func RemoveCallback(cb *CallbackDesc) { + bypassObj.mu.Lock() + defer bypassObj.mu.Unlock() + for _, h := range bypassObj.handles { + h.notify <- notifyObj{cb: cb, remove: true} + } +} + +func StopBypassService() { + bypassObj.mu.Lock() + defer bypassObj.mu.Unlock() + for _, h := range bypassObj.handles { + h.Stop() + } + bypassObj.handles = nil +} diff --git a/gover/proxy/bypass/bypass_test.go b/gover/proxy/bypass/bypass_test.go new file mode 100644 index 00000000..a53b4cf0 --- /dev/null +++ b/gover/proxy/bypass/bypass_test.go @@ -0,0 +1,11 @@ +package bypass + +import ( + "fmt" + "net" + "testing" +) + +func TestBypass(t *testing.T) { + fmt.Println(net.Interfaces()) +} diff --git a/gover/proxy/handlers.go b/gover/proxy/handlers.go index 4cc9e835..0418bcfe 100644 --- a/gover/proxy/handlers.go +++ b/gover/proxy/handlers.go @@ -5,20 +5,46 @@ import ( "github.com/MoonlightPS/Iridium-gidra/gover/gen" "github.com/MoonlightPS/Iridium-gidra/gover/utils" + "github.com/yezihack/colorlog" ) -type Handler = func(*KCPConn, []byte) ([]byte, error) +type Handler = func(*KCPConn, []byte, bool) ([]byte, error) var handlersMap = map[int]Handler{} var b64 = base64.StdEncoding -func HandleGetPlayerTokenReq(conn *KCPConn, data []byte) ([]byte, error) { +func sniffSeed(mSeed, sentMs uint64) uint64 { + for times := uint64(0); times < 1e4; times++ { + seed := genPrngSeed(sentMs + times) + if seed == mSeed { + colorlog.Debug("seed found, seed: %d", seed) + prng = utils.NewCompatPrng(int32(sentMs + times)) + prng.SafeUInt64() + return seed + } + seed = genPrngSeed(sentMs - times) + if seed == mSeed { + colorlog.Debug("seed found, seed: %d", seed) + prng = utils.NewCompatPrng(int32(sentMs - times)) + prng.SafeUInt64() + return seed + } + } + colorlog.Debug("seed not found") + return 0 +} + +func HandleGetPlayerTokenReq(conn *KCPConn, data []byte, bypass bool) ([]byte, error) { msg, err := conn.parser.Parse(data) if err != nil { return nil, err } body := msg.Body.(*gen.GetPlayerTokenReq) + if bypass { + conn.clientSeed = msg.Header.GetSentMs() + return conn.parser.Compose(msg) + } seedEncrypted, err := b64.DecodeString(body.GetClientSeed()) if err != nil { @@ -30,13 +56,9 @@ func HandleGetPlayerTokenReq(conn *KCPConn, data []byte) ([]byte, error) { return nil, err } conn.seed = be.Uint64(seedBytes) + sniffSeed(conn.seed, msg.Header.GetSentMs()) - keyID := int(body.GetKeyId()) - if keyID == utils.CN_KEY { - keyID = utils.CN_SIGN_KEY - } else { - keyID = utils.OS_SIGN_KEY - } + keyID := int(body.GetKeyId()) + 1000 seedEncrypted, err = utils.Encrypt(seedBytes, keyID) if err != nil { return nil, err @@ -46,7 +68,7 @@ func HandleGetPlayerTokenReq(conn *KCPConn, data []byte) ([]byte, error) { return conn.parser.Compose(msg) } -func HandleGetPlayerTokenRsp(conn *KCPConn, data []byte) ([]byte, error) { +func HandleGetPlayerTokenRsp(conn *KCPConn, data []byte, bypass bool) ([]byte, error) { msg, err := conn.parser.Parse(data) if err != nil { return nil, err @@ -63,7 +85,14 @@ func HandleGetPlayerTokenRsp(conn *KCPConn, data []byte) ([]byte, error) { if err != nil { return nil, err } + + if bypass { + conn.seed = be.Uint64(seedBytes) + return conn.parser.Compose(msg) + } + conn.seed = be.Uint64(seedBytes) ^ conn.seed + colorlog.Info("get server key: %d", conn.seed) signature, err := utils.Sign(seedBytes, utils.SIGN_KEY) if err != nil { @@ -76,17 +105,20 @@ func HandleGetPlayerTokenRsp(conn *KCPConn, data []byte) ([]byte, error) { return ret, err } -func HandlePlayerLoginReq(conn *KCPConn, data []byte) ([]byte, error) { +func HandlePlayerLoginReq(conn *KCPConn, data []byte, bypass bool) ([]byte, error) { + if bypass { + return data, nil + } msg, err := conn.parser.Parse(data) if err != nil { return nil, err } body := msg.Body.(*gen.PlayerLoginReq) - if conn.keyID == utils.CN_KEY { - body.Checksum = "64309cf5f6d6b7c427d3e15622636372c14bc8ce7252be4bd27e9a1866b688c226" + if conn.keyID == utils.CN_KEY || conn.keyID == utils.CN1_KEY { + body.Checksum = "4fa709ab639fc791f8288975f0428f0c912b36de981c9553145ce0c7a35f088725" } else { - body.Checksum = "eb8aeaf9f40c5bc5af2ac93ad1da07fa05acf5206fe08c10290357a414aecb7c24" + body.Checksum = "c1fed3cda007abe60b0d17c6c7a5442aec6d3bc7770693949a7b1ab0483fc16225" } return conn.parser.Compose(msg) diff --git a/gover/proxy/socket.go b/gover/proxy/socket.go index 63556870..d06dd7db 100644 --- a/gover/proxy/socket.go +++ b/gover/proxy/socket.go @@ -32,13 +32,16 @@ type KCPConn struct { running bool keyID int seed uint64 + clientSeed uint64 + recv []byte + send []byte once sync.Once } func (c *KCPConn) Start() { go func() { // recv from server - buf := make([]byte, BUFFER_SIZE) + buf := make([]byte, 4096) remote, server := c.remote, c.server for { n, err := remote.Read(buf) @@ -69,12 +72,12 @@ func (c *KCPConn) Start() { continue } - n = server.Recv(buf) + n = server.Recv(c.recv) for n > 0 { packet := make([]byte, n) - copy(packet, buf) + copy(packet, c.recv) c.sChan <- packet - n = server.Recv(buf) + n = server.Recv(c.recv) } } }() @@ -115,7 +118,7 @@ func (c *KCPConn) Start() { // colorlog.Debug("client recv packet cmd:%d, n:%d", cmd, len(packet)) if handler, ok := handlersMap[cmd]; ok { - packet, err = handler(c, packet) + packet, err = handler(c, packet, false) if err != nil { colorlog.Error("handle client packet %d failed! err: %+v", cmd, err) continue @@ -150,7 +153,7 @@ func (c *KCPConn) Start() { // } if handler, ok := handlersMap[cmd]; ok { - packet, err = handler(c, packet) + packet, err = handler(c, packet, false) if err != nil { colorlog.Error("handle server packet %d failed! err: %+v", cmd, err) continue @@ -175,12 +178,12 @@ func (c *KCPConn) Input(data []byte, size int) int { if res != 0 { return res } - n := c.client.Recv(data) + n := c.client.Recv(c.send) for n > 0 { packet := make([]byte, n) - copy(packet, data) + copy(packet, c.send) c.cChan <- packet - n = c.client.Recv(data) + n = c.client.Recv(c.send) } return 0 } @@ -190,9 +193,13 @@ func (c *KCPConn) Close(hs *Handshake) { if hs == nil { hs = NewHandshakePacket(HANDSHAKE_FIN, c.hs.conv, c.hs.token, 1, 0x19419494) } - c.remote.Write(hs.Compose()) - c.remote.Close() - c.writeLocal(hs.Compose()) + if c.remote != nil { + c.remote.Write(hs.Compose()) + c.remote.Close() + } + if c.writeLocal != nil { + c.writeLocal(hs.Compose()) + } c.running = false close(c.cChan) close(c.sChan) @@ -289,6 +296,8 @@ func ConstructKCPConn(remote *net.UDPAddr, writeLocal WriteFunc, hs *Handshake, hs: handshake, running: true, keyID: keyID, + recv: make([]byte, BUFFER_SIZE), + send: make([]byte, BUFFER_SIZE), } kConn.Start() @@ -310,7 +319,7 @@ func (k *KCPSocket) Start() { k.conns = &sync.Map{} go func() { // recv from local - buf := make([]byte, BUFFER_SIZE) + buf := make([]byte, 4096) local, remote, key, keyID, conns := k.local, k.remote, k.key, k.keyID, k.conns for { n, addr, err := local.ReadFrom(buf) @@ -369,7 +378,12 @@ func (s *KCPSocket) Stop() { s.conns = nil } -func NewKCPSocket(local *net.UDPConn, remote *net.UDPAddr, dispatchKey *utils.PacketKey, keyID int) *KCPSocket { +type ProxyInterface interface { + Start() + Stop() +} + +func NewKCPSocket(local *net.UDPConn, remote *net.UDPAddr, dispatchKey *utils.PacketKey, keyID int) ProxyInterface { return &KCPSocket{ local: local, remote: remote, diff --git a/gover/utils/crypto.go b/gover/utils/crypto.go index 8f6f9227..bf98a257 100644 --- a/gover/utils/crypto.go +++ b/gover/utils/crypto.go @@ -20,11 +20,15 @@ const ( ) const ( - CN_KEY = 2 - OS_KEY = 3 - SIGN_KEY = 0 - CN_SIGN_KEY = 4 - OS_SIGN_KEY = 5 + CN_KEY = 2 + OS_KEY = 3 + SIGN_KEY = 0 + CN1_KEY = 4 + OS1_KEY = 5 + CN_SIGN_KEY = 1000 + 2 + OS_SIGN_KEY = 1000 + 3 + CN1_SIGN_KEY = 1000 + 4 + OS1_SIGN_KEY = 1000 + 5 ) // toPrivateKey convert bytes to private key @@ -89,6 +93,22 @@ func InitKey(p string) error { if err != nil { return err } + err = loadKey(CN1_KEY, path.Join(p, "MHYPrivCN1.pem")) + if err != nil { + return err + } + err = loadKey(OS1_KEY, path.Join(p, "MHYPrivOS1.pem")) + if err != nil { + return err + } + err = loadKey(CN1_SIGN_KEY, path.Join(p, "MHYSignCN1.pem")) + if err != nil { + return err + } + err = loadKey(OS1_SIGN_KEY, path.Join(p, "MHYSignOS1.pem")) + if err != nil { + return err + } return nil } diff --git a/gover/utils/prng.go b/gover/utils/prng.go new file mode 100644 index 00000000..9eb90d7a --- /dev/null +++ b/gover/utils/prng.go @@ -0,0 +1,101 @@ +package utils + +import "math" + +type CompatPrng struct { + seedArray []int32 + inext int32 + inextp int32 +} + +func abs(x int32) int32 { + if x < 0 { + x = -x + } + return x +} + +func (c *CompatPrng) SafeUInt64() uint64 { + return uint64(c.Sample() * math.MaxUint64) +} + +func (c *CompatPrng) Sample() float64 { + return float64(c.sample()) * (1.0 / float64(math.MaxInt32)) +} + +func (c *CompatPrng) sample() int32 { + locINext := c.inext + 1 + if locINext >= 56 { + locINext = 1 + } + + locINextp := c.inextp + 1 + if locINextp >= 56 { + locINextp = 1 + } + + seedArray := c.seedArray + retVal := seedArray[locINext] - seedArray[locINextp] + + if retVal == math.MaxInt32 { + retVal-- + } + if retVal < 0 { + retVal += math.MaxInt32 + } + + seedArray[locINext] = retVal + c.inext = locINext + c.inextp = locINextp + + return retVal +} + +func NewCompatPrng(seed int32) *CompatPrng { + seedArray := make([]int32, 56) + + var subtraction int32 = abs(seed) + if seed == math.MinInt32 { + subtraction = math.MaxInt32 + } + mj := 161803398 - subtraction + seedArray[55] = mj + var mk int32 = 1 + + var ii int32 = 0 + for i := int32(1); i < 55; i++ { + // The range [1..55] is special (Knuth) and so we're wasting the 0'th position. + ii += 21 + if (ii) >= 55 { + ii -= 55 + } + + seedArray[ii] = mk + mk = mj - mk + if mk < 0 { + mk += math.MaxInt32 + } + + mj = seedArray[ii] + } + + for k := int32(1); k < 5; k++ { + for i := int32(1); i < 56; i++ { + n := i + 30 + if n >= 55 { + n -= 55 + } + + seedArray[i] -= seedArray[1+n] + if seedArray[i] < 0 { + seedArray[i] += math.MaxInt32 + } + } + } + + return &CompatPrng{ + seedArray: seedArray, + inext: 0, + inextp: 21, + } +} diff --git a/gover/utils/prng_test.go b/gover/utils/prng_test.go new file mode 100644 index 00000000..f9a6fbd3 --- /dev/null +++ b/gover/utils/prng_test.go @@ -0,0 +1,15 @@ +package utils + +import ( + "fmt" + "testing" +) + +func TestPrng(t *testing.T) { + p := NewCompatPrng(1024) + v := p.SafeUInt64() + fmt.Println(v) + if v != 12723918419362635776 { + t.Fail() + } +} diff --git a/gover/utils/recorder.go b/gover/utils/recorder.go index cfc7fef8..fd38781f 100644 --- a/gover/utils/recorder.go +++ b/gover/utils/recorder.go @@ -87,7 +87,7 @@ func (r *Recorder) Start() { // process data d, err := parser.Parse(data.packet) if err != nil { - colorlog.Warn("parse packet data failed") + colorlog.Warn("parse packet data failed, err: %+v", err) continue } @@ -116,7 +116,7 @@ func (r *Recorder) Start() { Time: data.time, Object: body, } - colorlog.Info("Record %s -> %s %5d: %s", SourceDesc(data.source^1), pack.Source, data.cmd, pack.ProtoName) + colorlog.Info("Record %s -> %s %5d: %s", pack.Source, SourceDesc(data.source^1), data.cmd, pack.ProtoName) r.packets = append(r.packets, pack) } colorlog.Warn("recorder quit") diff --git a/keys/MHYPrivCN1.pem b/keys/MHYPrivCN1.pem new file mode 100644 index 00000000..314f0f83 --- /dev/null +++ b/keys/MHYPrivCN1.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyaxqjPJP5+Innfv5IdfQqY/ftS++lnDRe3EczNkIjESWXhHS +OljEw9b9C+/BtF+fO9QZL7Z742y06eIdvsMPQKdGflB26+9OZ8AF4SpXDn3aVWGr +8+9qpB7BELRZI/Ph2FlFL4cobCzMHunncW8zTfMId48+fgHkAzCjRl5rC6XT0Yge +6+eKpXmF+hr0vGYWiTzqPzTABl44WZo3rw0yurZTzkrmRE4kR2VzkjY/rBnQAbFK +KFUKsUozjCXvSag4l461wDkhmmyivpNkK5cAxuDbsmC39iqagMt9438fajLVvYOv +pVs9ci5tiLcbBtfB4Rf/QVAkqtTm86Z0O3e7DwIDAQABAoIBAQCyma226vTW35LE +N5zXWuAg+hhcxk6bvofWMUMXKvGF/0vHPTMXlvuSkDeDNa4vBivneRthBNPMgb3q +DuTWxrogQMOOI8ZdhY3DFexfDvcQD2anDJuSqSmg9Nd36q+yxk3xIoXB5Ilo23dd +vTnJXHhsBNovv7zRLO134cAHFqDoKzt5EEHre0skUcn6HjHOek6c53jvpKr5LSrr +iwx5gMuY/7ZSIUDo9WGY70qbQFGY6bOlX9x8uNjcFF+7SztEVQ+vhJ/+7EvwqaJr +ysweo0l91TKM9WaMuwoucKeceVWuynEw6GGTw8UTLtltekLGe6bS8YxY8fVwnKkT +RwJYwAJRAoGBAP2rhcfOA+1Ja37hUHKebfp9rHsex4+pGyt3Kdu7WdqOn4sexmya +BuiHQcUchPDVla/ruQZ20+8LHgzBDo0m8sY7gpf715UV9NSVIRD0wu26SKRklOFz +J4HBOwU9hBGLSnRUJzyvVlt5O7E9hAv61SCrvWBEcow2YnKNQLwvjMVJAoGBAMuG +oSb3A/ulqtp2zpxVAclYe/bSItZZTOUWP6Vb4hOiHxIJ0n1H9ap6grOYkJ/Yn4gg +yYzKm/noF1wXP7Rj/xOahnvMkzhGdmOabvE9LH5HwQTWxBBWTkZzgBbYtbg+J5MT +cKqJaychSRjJj+xX+d90rtlSu/c27chlSRKAHXWXAoGAFTcIHDq9l1XBmL3tRXi8 +h+uExlM/q2MgM5VmucrEbAPrke4D+Ec1drMBLCQDdkTWnPzg34qGlQJgA/8NYX61 +ZSDK/j0AvaY1cKX8OvfNaaZftuf2j5ha4H4xmnGXnwQAORRkp62eUk4kUOFtLrdO +pcnXL7rpvZI6z4vCszpi0okCgYEAp3lZEl8g/+oK9UneKfYpSi1tlGTGFevVwozU +QpWhKta1CnraogycsnOtKWvZVi9C1xljwF7YioPY9QaMfTvroY3+K9DjM+OHd96U +fB4Chsc0pW60V10te/t+403f+oPqvLO6ehop+kEBjUwPCkQ6cQ3q8xmJYpvofoYZ +4wdZNnECgYBwG8Vrv7Z+kX9Zuh1FvcRoY57bYLU0cWW92SA3Nvi8pZOIEaLHrQyZ +pvvaLIicR1m9+KsOAmii7ru0zL7KsrGW+5migQsaDi4gzahKQpad/R7MLKi/L53r +Ymo0aZKARLHW82GbomQ0zxdRoo9vaqfGNpXkxyyt3k3GGDunmrskYw== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/keys/MHYPrivOS1.pem b/keys/MHYPrivOS1.pem new file mode 100644 index 00000000..f1636adb --- /dev/null +++ b/keys/MHYPrivOS1.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAsJbFp3WcsiojjdQtVnTuvtawL2m4XxK93F6lCnFwcZqUP39t +xFGGlrogHMqreyawIUN7E5shtwGzigzjW8Ly5CryBJpXP3ehNTqJS7emb+9LlC19 +Oxa1eQuUQnatgcsd16DPH7kJ5JzN3vXnhvUyk4Qficdmm0uk7FRaNYFi7EJs4xyq +FTrp3rDZ0dzBHumlIeK1om7FNt6Nyivgp+UybO7kl0NLFEeSlV4S+7ofitWQsO5x +YqKAzSzz+KIRQcxJidGBlZ1JN/g5DPDpx/ztvOWYUlM7TYk6xN3focZpU0kBzAw/ +rn94yW9z8jpXfzk+MvWzVL/HAcPy4ySwkay0NwIDAQABAoIBADzKWpawbVYEHaM4 +lLb7oCjAPXzE9zx7djLDvisfLCdfoINPedkoe52ty1o+BtRpWB7LXTY9pFic1FLE +5wvyy6zyf8hH3ZsysqNhWFxhh4FnLmx/UGokAir+anaK5mYVJ1vQtxzjlV1HAbQs +kRyrklKoHDdRFqiFXOwiib97oDNWhD+RxfyGwwJnynZZSXdLbLSiz/QHQNr/+Ufk +KRBaxt0CfU7mOLZxoy6fNAxHdBcBJPHCyh+aDvEbix7nSncSU8Ju/48YJ8DrglbZ +sXCYoA5Uz8NMDuaEMgoNWCFQVoEcRkEUoaH7BlWd3UUFRPnDZ1B4BmkrVoRE8a58 +3OqSwakCgYEA19wQUISXtpnmCrEZfbyZ6IwOy8ZCVaVUtbTjVa8UyfNglzzJG3yz +cXU3X35v5/HNCHaXbG2qcbQLThnHBA+obW3RDo+Q49V84Zh1fUNH0ONHHuC09kB/ +/gHqzn/4nLf1aJ2O0NrMyrZNsZ0ZKUKQuVCqWjBOmTNUitcc8RpXZ8sCgYEA0W09 +POM/It7RoVGI+cfbbgSRmzFo9kzSp5lP7iZ81bnvUMabu2nv3OeGc3Pmdh1ZJFRw +6iDM6VVbG0uz8g+f8+JT32XdqM7MJAmgfcYfTVBMiVnh330WNkeRrGWqQzB2f2Wr ++0vJjU8CAAcOWDh0oNguJ1l1TSyKxqdL8FsA38UCgYEAudt1AJ7psgOYmqQZ+rUl +H6FYLAQsoWmVIk75XpE9KRUwmYdw8QXRy2LNpp9K4z7C9wKFJorWMsh+42Q2gzyo +HHBtjEf4zPLIb8XBg3UmpKjMV73Kkiy/B4nHDr4I5YdO+iCPEy0RH4kQJFnLjEcQ +LT9TLgxh4G7d4B2PgdjYYTkCgYEArdgiV2LETCvulBzcuYufqOn9/He9i4cl7p4j +bathQQFBmSnkqGQ+Cn/eagQxsKaYEsJNoOxtbNu/7x6eVzeFLawYt38Vy0UuzFN5 +eC54WXNotTN5fk2VnKU4VYVnGrMmCobZhpbYzoZhQKiazby/g60wUtW9u7xXzqOd +M/428YkCgYBwbEOx1RboH8H+fP1CAiF+cqtq4Jrz9IRWPOgcDpt2Usk1rDweWrZx +bTRlwIaVc5csIEE2X02fut/TTXr1MoXHa6s2cQrnZYq44488NsO4TAC26hqs/x/H +bVOcX13gT26SYngAHHeh7xjWJr/KgIIwvcvgvoVs6lu7a8aLUvrOag== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/keys/MHYSignCN1.pem b/keys/MHYSignCN1.pem new file mode 100644 index 00000000..85f54e2f --- /dev/null +++ b/keys/MHYSignCN1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlCwdYrveozYYcKOSz4cj +BfORvd6POZSxsM9JybWvTb9rr1qGhulgoNcMB0sUA4XnfNlt/aaT+JKSTEgynyX8 +of74Nmu70MRO2Nemi0YnI56gK2f0tIdmpFKnojgDTlLslQnKBzcK/elbcX2XE3FM +K/hA2rkJBIMkIsXJ23nfWy/6KFB/nhXft+wzDahYmzaoLKsgq4xQInB6n0dUSkFN +SMV+98CRjh+Y7pXlyEglDXxj+IhBVsl8s41c9vmgLHWS7feMufbeqko83fLv2GlI +/aU0pvmYr9Lyf4kgPMp5aTqeyCm/ztb3bp5QoW7S2hlGP6gtxGr4s/lMpZN5YgTZ +bQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/keys/MHYSignOS1.pem b/keys/MHYSignOS1.pem new file mode 100644 index 00000000..263dee62 --- /dev/null +++ b/keys/MHYSignOS1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA15RBm/vARY0axYksImhs +Ticpv09OYfS4+wCvmE7psOvZhW2URZ2Rlf5DsEtuRG/7v5W/2obqqVkf+1dorPcR +2iqrYZ4VVPf7KU3Cgqh0kzLGxWOpGxzwJULEyFVaiMDWbk7gr8rik/jYyhLiLc52 +zz3E3whTUPleKhOhXnxx1iOKY+TPVI8jJfDNiQoh0UvgjnkigJ/saPzjogeig/4M +cBc4l5cDkvttkKQKq7oXe9OCBClgKlYjcc1CNalwMlTz7NvLEko+ZLTgpA+kElZu +myBXT67mmW7t7IDXorscAI7auwusKWmq797alFkQ/6sUqs8KKGnqQ2fwHfa/RYDh +EwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file