-
-
Save mStirner/35513b14e0872c55ba9465a7e4574989 to your computer and use it in GitHub Desktop.
Samsung Smart TV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INFO[0000] Using config file: /home/marc/.config/samtvcli/samtvcli.yaml | |
DEBU[0000] Fetch URL: http://192.168.2.100:8000/socket.io/1/?t=1707330078650 | |
DEBU[0000] Reading WS message... | |
DEBU[0000] Read message (type 1): `1::` | |
DEBU[0000] Got greetings from TV | |
DEBU[0000] Sending SmartView handshake... | |
DEBU[0000] Sending WS message: `1::/com.samsung.companion` ... | |
DEBU[0000] Reading WS message... | |
DEBU[0000] Read message (type 1): `1::/com.samsung.companion` | |
DEBU[0000] SmartView handshake completed | |
DEBU[0000] Reading WS message... | |
DEBU[0000] sendMessage('KEY_MUTE') | |
DEBU[0000] Sending WS message: `5::/com.samsung.companion:{"name":"callCommon","args":[{"Session_Id":2,"body":"[64, 195, 236, 252, 204, 98, 232, 235, 62, 167, 133, 251, 240, 61, 113, 151, 63, 252, 27, 93, 117, 22, 145, 225, 113, 113, 48, 82, 233, 46, 177, 213, 32, 133, 65, 212, 198, 2, 43, 242, 19, 108, 120, 65, 73, 51, 27, 140, 124, 37, 28, 86, 240, 221, 46, 97, 177, 232, 36, 124, 239, 167, 94, 141, 16, 234, 165, 211, 108, 249, 25, 217, 169, 70, 149, 85, 105, 244, 133, 144, 118, 163, 115, 8, 243, 178, 199, 161, 155, 49, 221, 159, 142, 140, 108, 56, 209, 227, 200, 238, 112, 204, 240, 136, 211, 89, 49, 224, 101, 54, 82, 156, 87, 173, 233, 109, 91, 206, 0, 239, 143, 24, 131, 90, 235, 212, 109, 39, 175, 106, 58, 69, 184, 85, 186, 25, 4, 87, 64, 255, 105, 236, 37, 63, 185, 226, 133, 27, 51, 153, 76, 251, 96, 155, 200, 10, 77, 28, 104, 147, 148, 17, 152, 36, 205, 239, 150, 138, 68, 148, 23, 248, 146, 63, 106, 152]"}]}` ... | |
DEBU[0000] Read message (type 1): `5::/com.samsung.companion:{"name":"receiveCommon","args":"[138,236,222,172,138,17,113,65,75,30,153,51,130,19,35,241,175,31,16,185,28,105,209,7,96,36,49,144,63,29,186,167,18,149,190,209,125,182,159,1,49,53,79,171,153,239,248,77,38,185,38,14,169,7,17,251,210,154,81,6,123,224,39,54]"}` | |
DEBU[0000] SmartView message received | |
DEBU[0000] data: f7bd4e40add808dd31db0a14282a1f2d | |
DEBU[0000] padlen: 2d | |
ERRO[0000] Could not parse message: cannot decrypt response: invalid padding | |
DEBU[0000] Reading WS message... | |
ERRO[0005] Cannot send key 'KEY_MUTE': no reply from TV |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// THE FOLLOWING CODE WAS CREATE BY CHATGPT | |
// ITS A NODE.JS PORT OF "src/smart.php" | |
// | |
const fetch = require('node-fetch').default; | |
const readline = require("readline"); | |
const {exec} = require("child_process"); | |
const crypto = require("crypto"); | |
function sha1(input) { | |
return crypto.createHash('sha1').update(input).digest('hex'); | |
} | |
function hex2bin(hexString) { | |
return Buffer.from(hexString, 'hex'); | |
} | |
const UserId = "654321"; | |
const AppId = "12345"; | |
let dest_hash = ""; | |
let SKPrime = ""; | |
let ctx = ""; | |
// const Guid = "7e509404-9d7c-46b4-8f6a-e2a9668ad184"; | |
const deviceId = "784c5390-73a2-441e-91d6-1980b114e866"; | |
let hash = ""; | |
let AES_key = ""; | |
let lastRequestId = ""; | |
let sessionId = 0; | |
const tvIP = "192.168.2.100"; | |
const tvPort = "8080"; | |
function runSmartCrypto(args){ | |
return new Promise((resolve, reject) => { | |
exec(`./bin/smartcrypto ${args}`, (err, stdout) => { | |
if(err){ | |
reject(err); | |
}else{ | |
resolve(stdout); | |
} | |
}); | |
}); | |
} | |
function getFullUrl(urlPath) { | |
return `http://${tvIP}:${tvPort}${urlPath}`; | |
} | |
function getFullRequestUri(step, appId, deviceId) { | |
return getFullUrl(`/ws/pairing?step=${step}&app_id=${appId}&device_id=${deviceId}`); | |
} | |
async function checkPinPageOnTv() { | |
const fullUrl = getFullUrl("/ws/apps/CloudPINPage"); | |
const response = await fetch(fullUrl); | |
const page = await response.text(); | |
const stateMatch = page.match(/<state>([^<>]*)<\/state>/si); | |
console.log("Current state: ", stateMatch[1]); | |
return stateMatch[1] === "stopped"; | |
} | |
async function showPinPageOnTv() { | |
await fetch(getFullUrl("/ws/apps/CloudPINPage"), { method: "POST", body: "pin4" }); | |
} | |
async function startPairing() { | |
lastRequestId = 0; | |
if (await checkPinPageOnTv()) { | |
console.log("Pin NOT on TV"); | |
await showPinPageOnTv(); | |
} else { | |
console.log("Pin ON TV"); | |
} | |
} | |
async function firstStepOfPairing() { | |
const firstStepURL = getFullRequestUri(0, AppId, deviceId) + "&type=1"; | |
const firstStepResponse = await fetch(firstStepURL); | |
// Process the response if needed | |
} | |
async function generateServerHello(pin) { | |
const res = await runSmartCrypto(`generateServerHello ${UserId} ${pin}`); | |
const match = res.match(/AES key: ([^ \n]*).*hash: ([^ \n]*).*ServerHello: ([^ \n]*)/si); | |
AES_key = match[1]; | |
hash = match[2]; | |
return match[3]; | |
} | |
async function parseClientHello(clientHello) { | |
const res = await runSmartCrypto(`parseClientHello ${clientHello} ${hash} ${AES_key} ${UserId}`); | |
const regex = new RegExp(/dest_hash: ([^ \n]*).*SKPrime: ([^ \n]*).*ctx: ([^ \n]*)/, "si"); | |
const match = res.match(regex); | |
dest_hash = match[1]; | |
SKPrime = match[2]; | |
ctx = match[3]; | |
console.log("dest_hash: ", dest_hash); | |
console.log("SKPrime: ", SKPrime); | |
console.log("ctx: ", ctx); | |
return true; | |
} | |
async function helloExchange(pin) { | |
const serverHello = await generateServerHello(pin); | |
if (!serverHello) { | |
return false; | |
} | |
const content = `{"auth_Data":{"auth_type":"SPC","GeneratorServerHello":"${serverHello}"}}`; | |
const secondStepURL = getFullRequestUri(1, AppId, deviceId); | |
const secondStepResponse = await fetch(secondStepURL, { method: "POST", body: content }); | |
const body = await secondStepResponse.text() | |
const regex = new RegExp(/request_id.*?(\d).*?GeneratorClientHello.*?:.*?(\d[0-9a-zA-Z]*)/, "si"); | |
const match = body.match(regex); | |
const requestId = match[1]; | |
const clientHello = match[2]; | |
lastRequestId = requestId; | |
return parseClientHello(clientHello); | |
} | |
function generateServerAcknowledge() { | |
const SKPrimeHash = sha1(hex2bin(SKPrime + "01")); | |
return "0103000000000000000014" + SKPrimeHash.toUpperCase() + "0000000000"; | |
} | |
function parseClientAcknowledge(clientAck) { | |
const SKPrimeHash = sha1(hex2bin(SKPrime + "02")); | |
const tmpClientAck = "0104000000000000000014" + SKPrimeHash.toUpperCase() + "0000000000"; | |
return clientAck === tmpClientAck; | |
} | |
async function acknowledgeExchange() { | |
const serverAckMessage = generateServerAcknowledge(); | |
const content = `{"auth_Data":{"auth_type":"SPC","request_id":"${lastRequestId}","ServerAckMsg":"${serverAckMessage}"}}`; | |
const thirdStepURL = getFullRequestUri(2, AppId, deviceId); | |
const thirdStepResponse = await fetch(thirdStepURL, { method: "POST", body: content }); | |
const body = await thirdStepResponse.text(); | |
if (body.includes("secure-mode")) { | |
console.log("TODO: Implement handling of encryption flag!!!!"); | |
process.exit(-1); | |
} | |
const match = body.match(/ClientAckMsg.*?:.*?(\d[0-9a-zA-Z]*).*?session_id.*?(\d)/si); | |
if (!match) { | |
console.log("Unable to get session_id and/or ClientAckMsg!!!", body); | |
process.exit(-1); | |
} | |
const clientAck = match[1]; | |
const sessionId = match[2]; | |
console.log("sessionId: ", sessionId); | |
if (!parseClientAcknowledge(clientAck)) { | |
console.log("Parse client ac message failed."); | |
process.exit(-1); | |
} | |
return sessionId; | |
} | |
async function closePinPageOnTv() { | |
await fetch(getFullUrl("/ws/apps/CloudPINPage/run"), { method: "DELETE" }); | |
} | |
async function main() { | |
await startPairing(); | |
let pinAccepted = false; | |
console.log("Please enter pin from tv:"); | |
const rl = readline.createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
rl.question("PIN:", async (tvPIN) => { | |
console.log("Got pin: ", tvPIN); | |
await firstStepOfPairing(); | |
pinAccepted = await helloExchange(tvPIN); | |
if (pinAccepted) { | |
console.log("Pin accepted :)\n\n"); | |
} else { | |
console.log("Pin incorrect. Please try again...\n\n"); | |
} | |
await acknowledgeExchange(); | |
await closePinPageOnTv(); | |
console.log("Authorization successful :)\n\n"); | |
}); | |
} | |
main(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment