Skip to content

Instantly share code, notes, and snippets.

@mStirner
Created February 7, 2024 18:47
Show Gist options
  • Save mStirner/35513b14e0872c55ba9465a7e4574989 to your computer and use it in GitHub Desktop.
Save mStirner/35513b14e0872c55ba9465a7e4574989 to your computer and use it in GitHub Desktop.
Samsung Smart TV
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
//
// 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