├── reverse.txt ├── run.sh ├── cmd.go └── pairing.go /reverse.txt: -------------------------------------------------------------------------------- 1 | [0] = version = 1; [1] = cmd ; [2] = MSB len, [3] = LSB len 2 | 3 | min sizes = { 4 | [0] = 12; 5 | [2] = 16; 6 | [3] = 5; 7 | [5] = 0; 8 | [6] = 0; 9 | [7] = 0; 10 | [8] = 4; 11 | [9] = 1; 12 | [10] = 1; 13 | } 14 | 15 | cmd = 0 ====> int + int + byte + byte + byte + byte + optional { byte length + string } 16 | configure { width, height, maxPointers, inputMode } 17 | cmd = 14 ===> voice packet (whichever size you want) 18 | cmd = 21 ====> PING? PONG? Disqble watchdog? 19 | cmd = 2 ==> handle key long(ignored) + int + int 20 | cmd = 3 ===> pointer 21 | 22 | cmd = 18 => subcmds 1-16 IME related stuff like getSelectedText 23 | cmd = 19 => {byte} client became interactive 24 | 25 | cmd = 28 => Take bug report 26 | cmd = 29 = cancel bug report 27 | cmd = 11 = voice start 28 | cmd = 12 = voice stop 29 | cmd = 13 {int,int,int} = voice config 30 | cmd = 15 string Input 31 | cmd = 16: stirng Intent 32 | cmd = 36, 37==> Related to Setup-Wizard 33 | 34 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVER=192.168.1.30 4 | 5 | if [ ! -f server.pub ];then 6 | # This will trigger a fail server side because we send no client certificate, but meh 7 | openssl s_client -connect "$SERVER":6467 | openssl x509 -pubkey -noout > server.pub 8 | fi 9 | 10 | if [ ! -f key.pem ] || [ ! -f cert.pem ];then 11 | openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days $((365*30)) -subj '/CN=anymote\/phhproduct\/phhdevice\/phhmodel\/5554463/' 12 | openssl rsa -in key.pem -pubout > key.pub 13 | fi 14 | 15 | jsonsize() { 16 | len="$(echo -n "$1" | wc -c)" 17 | len=$(printf '%08x' $len) 18 | (echo $len | xxd -r -p ; echo -n "$1") > request 19 | (echo $len | xxd -r -p ; echo -n "$1") 20 | } 21 | 22 | # Do this once to pair locale certificate with AndroidTV device 23 | if false;then 24 | ( 25 | # Pairing request 26 | jsonsize '{"status":200,"type":10, "payload": {"service_name": "phh", "client_name": "Phh"}}' 27 | 28 | # TODO: Receive type 11 = Pairing request ACK 29 | 30 | # Roles: 0 = unknown, 1 = input, 2 = display 31 | # Encodings: 0 = unknown, 1 = alphanumeric, 2 = numeric, 3 = hexadecimal, 4 = qrcode 32 | jsonsize '{"status":200,"type":20, "payload": {"input_encodings":[{"type":1,"symbol_length":10}], "output_encodings":[{"type":1,"symbol_length":10}], "preferred_role":1}}' 33 | 34 | # TODO: Receive type 20 = OPTIONS 35 | 36 | 37 | # Configuration send actual encoding and role 38 | jsonsize '{"status":200,"type":30, "payload": {"encoding": {"type":3, "symbol_length":4}, "client_role":1}}' 39 | 40 | # Send passcode 41 | read pass 42 | nonce="$(echo "$pass" |grep -oE '..$')" 43 | gammaCheck="$(echo "$pass" |grep -oE '^..')" 44 | 45 | result="$( 46 | ( 47 | 48 | openssl rsa -pubin -inform PEM -text -noout < key.pub |grep -A 100 Modulus |grep -v Expon |tail -n +2 |tr -d '\n: ' |sed -E 's/^(00)*//g' | xxd -r -p 49 | (echo -n 0; openssl rsa -pubin -inform PEM -text -noout < key.pub |sed -nE 's/.*Exponent:.*0x([0-9a-fA-F]*).*/\1/p') | xxd -r -p 50 | 51 | openssl rsa -pubin -inform PEM -text -noout < server.pub |grep -A 100 Modulus |grep -v Expon |tail -n +2 |tr -d '\n: ' |sed -E 's/^(00)*//g' | xxd -r -p 52 | # the "0" here is because openssl command will output 0x10001, which is 5 digits, but we ned a pair number of digits 53 | (echo -n 0; openssl rsa -pubin -inform PEM -text -noout < server.pub |sed -nE 's/.*Exponent:.*0x([0-9a-fA-F]*).*/\1/p') | xxd -r -p 54 | 55 | echo -n "$nonce" | xxd -r -p 56 | )|sha256sum |grep -oE '^[0-9a-fA-F]*')" 57 | resultGamma="$(echo "$result" |grep -oE '^..')" 58 | 59 | echo "Received gamma $gammaCheck expected $resultGamma" >&2 60 | 61 | jsonsize '{"status":200,"type":40, "payload": {"secret":"'$(echo "$result" |xxd -r -p |base64)'"}}' 62 | 63 | # TODO: Receive secret ACK 64 | 65 | ) | openssl s_client -cert cert.pem -key key.pem -quiet -connect "$SERVER":6467 > result 66 | fi 67 | 68 | ( 69 | # Commands format is [version] [cmdid] [MSB additional len] [LSB additional len] .... 70 | 71 | # Configure screen 1024x1024, one pointer, input mode 2 72 | echo '01 00 00 0c 00 00 04 00 00 00 04 00 01 02 00 00' | xxd -r -p 73 | 74 | sleep 1 75 | # Mark client as active (probably useless) 76 | # echo '01 13 00 01 01' |xxd -r -p 77 | # sleep .1 78 | 79 | # Send KEYACTION_DOWN (01) for KEYCODE_HOME (3) 80 | echo '01 02 00 10 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 03' | xxd -r -p 81 | 82 | #Send KEYACTION_UP (00) for KEYCODE_HOME (3) 83 | echo '01 02 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03' |xxd -r -p 84 | 85 | sleep 1 86 | 87 | #Send an intent 88 | echo '01 10 00 52' | xxd -r -p ; echo -n 'android-app://com.android.tv.settings/#Intent;action=android.settings.SETTINGS;end' 89 | 90 | #Send a string 91 | # echo '01 0f 00 05' | xxd -r -p ; echo -n 'hello' 92 | 93 | ) | openssl s_client -cert cert.pem -key key.pem -quiet -connect 192.168.1.30:6466 94 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/binary" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strconv" 11 | ) 12 | 13 | func send(conn *tls.Conn, cmdId byte, buf []byte) { 14 | header := make([]byte, 4) 15 | header[0] = 1 16 | header[1] = cmdId 17 | binary.BigEndian.PutUint16(header[2:], uint16(len(buf))) 18 | 19 | packet := append(header, buf...) 20 | fmt.Println("Writing packet ", len(packet)) 21 | conn.Write(packet) 22 | } 23 | 24 | func readString(buf []byte) []byte { 25 | if buf[0] == 1 { 26 | fmt.Println("Empty string") 27 | return buf[1:] 28 | } else { 29 | length := binary.BigEndian.Uint32(buf[1:]) 30 | fmt.Println("Got string ", string(buf[5:5+length])) 31 | return buf[5+length:] 32 | } 33 | } 34 | 35 | func recv(conn *tls.Conn) { 36 | header := make([]byte, 4) 37 | conn.Read(header) 38 | version := header[0] 39 | cmdid := header[1] 40 | 41 | length := binary.BigEndian.Uint16(header[2:]) 42 | var buf []byte 43 | if length > 0 { 44 | buf = make([]byte, length) 45 | conn.Read(buf) 46 | } 47 | 48 | fmt.Println("Received command", cmdid, " of length ", length, "version ", version) 49 | // Configure succeeded 50 | if cmdid == 7 { 51 | controllerNumber := binary.BigEndian.Uint32(buf) 52 | fmt.Println("Received configure succeeded ", controllerNumber) 53 | var pos []byte 54 | pos = readString(buf[4:]) // hash 55 | pos = readString(pos) // fingerprint 56 | pos = readString(pos) // id 57 | pos = readString(pos) // manufacturer 58 | pos = readString(pos) // model 59 | 60 | sdkInt := binary.BigEndian.Uint32(pos) 61 | fmt.Println(" sdk ", sdkInt) 62 | } 63 | } 64 | 65 | func configure(conn *tls.Conn, width int, height int, nPointers int, inputMode int) { 66 | packet := make([]byte, 12) 67 | binary.BigEndian.PutUint32(packet[0:], uint32(width)) 68 | binary.BigEndian.PutUint32(packet[4:], uint32(height)) 69 | packet[8] = byte(nPointers) 70 | packet[9] = byte(inputMode) 71 | 72 | send(conn, 0, packet) 73 | recv(conn) 74 | } 75 | 76 | func sendKeyevent(conn *tls.Conn, keyAction int, keyCode int) { 77 | packet := make([]byte, 16) 78 | //Timestamp, ignored 79 | binary.BigEndian.PutUint64(packet[0:], uint64(0)) 80 | binary.BigEndian.PutUint32(packet[8:], uint32(keyAction)) 81 | binary.BigEndian.PutUint32(packet[12:], uint32(keyCode)) 82 | 83 | send(conn, 2, packet) 84 | recv(conn) 85 | } 86 | 87 | func pressKey(conn *tls.Conn, keyCode int) { 88 | sendKeyevent(conn, 1, keyCode) 89 | sendKeyevent(conn, 0, keyCode) 90 | } 91 | 92 | func sendIntent(conn *tls.Conn, intent string) { 93 | send(conn, 16, []byte(intent)) 94 | } 95 | 96 | func strToKey(key string) int { 97 | if key == "up" { 98 | return 19 99 | } else if key == "down" { 100 | return 20 101 | } else if key == "right" { 102 | return 22 103 | } else if key == "left" { 104 | return 21 105 | } else if key == "home" { 106 | return 3 107 | } else if key == "back" { 108 | return 4 109 | } else if key == "center" || key == "enter" { 110 | return 23 111 | } 112 | return -1 113 | } 114 | 115 | func main() { 116 | //TODO: verify servert cert 117 | customVerify := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 118 | for _, rawCert := range rawCerts { 119 | c, _ := x509.ParseCertificate(rawCert) 120 | fmt.Println("Received certificate subject = ", c.Subject) 121 | } 122 | return nil 123 | 124 | } 125 | 126 | // TODO: Generate keypair and pair if it doesn't exist 127 | myCert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | 132 | conn, err := tls.Dial("tcp", 133 | fmt.Sprintf("%s:6466", "192.168.1.34"), 134 | &tls.Config{ 135 | VerifyPeerCertificate: customVerify, 136 | InsecureSkipVerify: true, 137 | Certificates: []tls.Certificate{myCert}, 138 | }) 139 | if err != nil { 140 | panic("Failed to connect " + err.Error()) 141 | } 142 | 143 | recv(conn) 144 | configure(conn, 1024, 1024, 1, 2) 145 | if os.Args[1] == "key" { 146 | var key int 147 | key, err := strconv.Atoi(os.Args[2]) 148 | if err != nil { 149 | key = strToKey(os.Args[2]) 150 | } 151 | pressKey(conn, key) 152 | } else if os.Args[1] == "intent" { 153 | sendIntent(conn, os.Args[2]) 154 | } 155 | // Key 3 = HOME 156 | //pressKey(conn, 3) 157 | //sendIntent(conn, "android-app://com.android.tv.settings/#Intent;action=android.settings.SETTINGS;end") 158 | } 159 | -------------------------------------------------------------------------------- /pairing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/sha256" 9 | "encoding/base64" 10 | "encoding/binary" 11 | "encoding/hex" 12 | "encoding/json" 13 | "math/big" 14 | "math/rand" 15 | "fmt" 16 | "log" 17 | "os" 18 | ) 19 | 20 | const( 21 | PAIRING_PHASE_PAIRING_REQUEST = 10 22 | PAIRING_PHASE_PAIRING_REQUEST_ACK = 11 23 | // Sent by both side 24 | PAIRING_PHASE_OPTIONS = 20 25 | // This is the "merge" of OPTIONS from both client and server to decide one final protocol 26 | PAIRING_PHASE_CONFIGURE = 30 27 | PAIRING_PHASE_SECRET = 40 28 | ) 29 | 30 | func writeJsonSized(conn *tls.Conn, str string) { 31 | var buf = make([]byte, 4) 32 | binary.BigEndian.PutUint32(buf, uint32(len(str))) 33 | buf = append(buf, []byte(str)...) 34 | conn.Write(buf) 35 | } 36 | 37 | func readJsonSized(conn *tls.Conn) string { 38 | lenbuf := make([]byte, 4) 39 | conn.Read(lenbuf) 40 | l := binary.BigEndian.Uint32(lenbuf) 41 | fmt.Println("Received json of len", l) 42 | 43 | buf := make([]byte, l) 44 | conn.Read(buf) 45 | return string(buf) 46 | } 47 | 48 | func trimNullBytes(b []byte) []byte { 49 | var nNullBytes = 0 50 | for i := 0; b[i] == 0; i++ { nNullBytes++ } 51 | return b[nNullBytes:] 52 | } 53 | 54 | func main() { 55 | var serverCert *x509.Certificate 56 | customVerify := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 57 | for _, rawCert := range rawCerts { 58 | 59 | c, _ := x509.ParseCertificate(rawCert) 60 | fmt.Println("Received certificate subject = ", c.Subject) 61 | serverCert = c 62 | } 63 | return nil 64 | 65 | } 66 | 67 | // TODO: Generate keypair if it doesn't exist 68 | myCert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | pairingConn, err := tls.Dial("tcp", 74 | fmt.Sprintf("%s:6467", "192.168.1.34"), 75 | &tls.Config{ 76 | VerifyPeerCertificate: customVerify, 77 | InsecureSkipVerify: true, 78 | Certificates: []tls.Certificate{myCert}, 79 | }) 80 | if err != nil { 81 | panic("Failed to connect " + err.Error()) 82 | } 83 | var result map[string]interface{} 84 | 85 | writeJsonSized(pairingConn, `{"status":200,"type":10, "payload": {"service_name": "phh", "client_name": "Phh"}}`) 86 | json.Unmarshal([]byte(readJsonSized(pairingConn)), &result) 87 | fmt.Println("Got status", result["status"]) 88 | fmt.Println("Got result", result) 89 | 90 | writeJsonSized(pairingConn, `{"status":200,"type":20, "payload": {"input_encodings":[{"type":1,"symbol_length":10}], "output_encodings":[{"type":1,"symbol_length":10}], "preferred_role":1}}`) 91 | json.Unmarshal([]byte(readJsonSized(pairingConn)), &result) 92 | fmt.Println("Got status", result["status"]) 93 | fmt.Println("Got result", result) 94 | 95 | writeJsonSized(pairingConn, `{"status":200,"type":30, "payload": {"encoding": {"type":3, "symbol_length":4}, "client_role":1}}`) 96 | json.Unmarshal([]byte(readJsonSized(pairingConn)), &result) 97 | fmt.Println("Got status", result["status"]) 98 | fmt.Println("Got result", result) 99 | 100 | var code []byte 101 | if true { 102 | scanner := bufio.NewScanner(os.Stdin) 103 | 104 | fmt.Println("Code?") 105 | scanner.Scan() 106 | codeStr := scanner.Text() 107 | codeA, err := hex.DecodeString(codeStr) 108 | if err != nil { 109 | panic("Failed to connect " + err.Error()) 110 | } 111 | code = codeA 112 | } else { 113 | code = make([]byte, 2) 114 | code[0] = 0 115 | code[1] = byte(rand.Intn(255)) 116 | } 117 | 118 | myPubCert, _ := x509.ParseCertificate(myCert.Certificate[0]) 119 | myPubkey, _ := myPubCert.PublicKey.(*rsa.PublicKey) 120 | otherPubkey, _ := serverCert.PublicKey.(*rsa.PublicKey) 121 | 122 | var a *big.Int = big.NewInt(int64(0)) 123 | a.Abs(myPubkey.N) 124 | myModulo := trimNullBytes(a.Bytes()) 125 | a.Abs(big.NewInt(int64(myPubkey.E))) 126 | myExponent := trimNullBytes(a.Bytes()) 127 | 128 | a.Abs(otherPubkey.N) 129 | otherModulo := trimNullBytes(a.Bytes()) 130 | a.Abs(big.NewInt(int64(otherPubkey.E))) 131 | otherExponent := trimNullBytes(a.Bytes()) 132 | 133 | h := sha256.New() 134 | h.Write(myModulo) 135 | h.Write(myExponent) 136 | h.Write(otherModulo) 137 | h.Write(otherExponent) 138 | h.Write(code[len(code)/2:]) 139 | 140 | fmt.Println("Result hash is: ", hex.EncodeToString(h.Sum(nil))) 141 | 142 | b64 := base64.StdEncoding.EncodeToString(h.Sum(nil)) 143 | 144 | req := fmt.Sprintf(`{"status":200,"type":40, "payload": {"secret":"%s"}}`, b64) 145 | fmt.Println("Request is ", req) 146 | writeJsonSized(pairingConn, req) 147 | 148 | json.Unmarshal([]byte(readJsonSized(pairingConn)), &result) 149 | fmt.Println("Got status", result["status"]) 150 | fmt.Println("Got result", result) 151 | 152 | pairingConn.Close() 153 | 154 | status, _ := result["status"].(float64) 155 | if status == 200 { 156 | os.Exit(0) 157 | } else { 158 | os.Exit(1) 159 | } 160 | 161 | } 162 | 163 | --------------------------------------------------------------------------------