Showing posts with label NEC. Show all posts
Showing posts with label NEC. Show all posts

Sunday, December 8, 2019

PJLink Authentication and Security

I've already wrote one article about PJLink protocol https://g33ksblog.blogspot.com/2017/03/pjlink-network-control-for-projector.html

PJLink is a protocol developed by JBMIA for controlling projectors (and presentation displays for example from NEC) via LAN connections
It use get and set commands. You can get or set several thinks, Power on/off or get the status. Input for video and audio source. Volume and if it's projector you can work also with lens and apertur.
More documentation can be find here: http://pjlink.jbmia.or.jp/english/




Here we will concentrate in Authentication and Security.
There are two possibilities
  1. there is no authentication and PJLink device start the communication with: PJLINK 0
  2. there is authentication and PJLink device start communication with PJLINK 1 4bytes random number
The authentication process is as follows(schematics below):
  1. PJLink device send PJLINK 1 4bytes random number
  2. PJLink clinet will concatenate this 4bytes random number and password and do MD5 hash
  3. PJLink client will send MD5 Hash and command to PJLink device
  4. PJLink device do same MD5 Hash generation and compare the result with client response to verify password and either respond with command result or error message
To obtain access to PJLink device with authentication there two ways.
  1. as per point 4 above PJLink device is doing MD5(random number + password) => which means it have a password plain text (or symetricaly encrypted) somewhere localy, hence if you have other access to device you might be able to get it.
  2. we can simply do brute force attack and guess password (here  are scripts to do word list based brute force attack) the problem is that every login using random salt, which means you need to find really correct password (collision is no help)


Thursday, March 30, 2017

PJLink - network control for projector

PJLink is a protocol developed by JBMIA for controlling projectors (and presentation displays for example from NEC) via LAN connections
The protocol works over TCP/IP, by default it use port 4352. Protocol supports authentification, however as I didn't set password for PJLink on my NEC, it works without authentification. (Not sure if that would be same on other venodrs as well).
It use get and set commands. You can get or set several thinks, Power on/off or get the status. Input for video and audio source. Volume and if it's projector you can work also with lens and apertur.
The structure is prety easy header followed by command and parametr:

HeaderClassCommandSpaceParameter
%1POWR?

Detail protocol specification: http://pjlink.jbmia.or.jp/english/data/5-1_PJLink_eng_20131210.pdf
More documentation can be find here: http://pjlink.jbmia.or.jp/english/

Below examples and test was done on my NEC MultiSync V423, PJLink listen on port 4352 as standard suggest the other ports are 80 http for other settings and 7142 which is NEC prorietary remote control protocol, more can be found here.

root@raspberrypi:/home/pi# nmap -sT 10.0.0.40 -p 1-65535

Starting Nmap 6.00 ( http://nmap.org ) at 2017-03-24 21:00 CET
Nmap scan report for 10.0.0.40
Host is up (0.018s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE
80/tcp   open  http
4352/tcp open  unknown
7142/tcp open  unknown
MAC Address: 58:C2:32:97:3A:48 (Unknown)

Below are example use telnet to switch on the device, switch it off and get status:

Connection closed by foreign host.
root@raspberrypi:/home/pi# telnet 10.0.0.40 4352
Trying 10.0.0.40...
Connected to 10.0.0.40.
Escape character is '^]'.
PJLINK 0
%1POWR 1
%1POWR=OK
%1POWR 0
%1POWR=OK
%1POWR ?
%1POWR=0
^]
telnet> quit
Connection closed.

Here is Python example for Power On/Off
#! /usr/bin/python

#takes two arguments IP and Power ON|OFF
#example: ./PJLinkPower.py 10.0.0.40 OFF

import sys
import cgi
import socket

if len(sys.argv) == 1:
  form = cgi.FieldStorage()
  monitor_ip = form.getvalue('monitor_ip')
else:
  monitor_ip = sys.argv[1]
  power = sys.argv[2]

#define some constanst to make life easier
port = 4352
buffer_size = 1024

if (power == 'ON'):
  command = '%1POWR 1\r'
elif (power == 'OFF'):
  command = '%1POWR 0\r'
else:
  command = '%1POWR ?\r'
  print "Invalid command."
  print "Use: IP ON|OFF"
  print "getting current status of device:"

new = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
new.connect((monitor_ip, port))
recv_data = new.recv(buffer_size)
print recv_data
print "sending: ", command
new.send(command)
recv_data = new.recv(buffer_size)
print recv_data
new.close()

Here is example of use:
root@raspberrypi:/var/www/cgi-bin# ./PJLinkPower.py 10.0.0.40 ON
PJLINK 0
sending:  %1POWR 1
%1POWR=OK
root@raspberrypi:/var/www/cgi-bin# ./PJLinkPower.py 10.0.0.40 OFF
PJLINK 0
sending:  %1POWR 0
%1POWR=OK
root@raspberrypi:/var/www/cgi-bin#
root@raspberrypi:/var/www/cgi-bin# ./PJLinkPower.py 10.0.0.40 TEST
Invalid command.
Use: IP ON|OFF
getting current status of device:
PJLINK 0
sending:  %1POWR ?
%1POWR=0

Monday, May 4, 2015

Home entertainment system monitoring and auto switch off

I have few devices which are creating my home entertainment system:

  • NEC monitor (sometimes good monitor is better then TV)
  • Yamaha HTR-4065 
  • PC Zotac
  • Evolveo DaulCoder DVB-T
  • XBOX 360

All those are connected to network via ethernet and there is some way to monitor or even to remotely control, lets start one by one:

DVB-T and PC

As you might know one of the easiest way to check if device is up or down would be to ping it, however in case of audio/video systems it's bit hard because if they are in Standby mode they will answer to the ping.
In some cases I don't even give the device permanent IP(DVB-T and PC). However there is the way to discover them via UPnP.

There is linux utility gssdp-discovery which use ssdp to discover UPnP capable device and as far as each device have unique identificator uuid we can check if device is running by checking if device is responding to ssdp discovery.

Below is small script which check based on uuid if device is running, which IP it was assigned and on which port UPnP is listening and record this to sqlite database.

#! /bin/bash

#test if device can be discovered using SSDP
#IMPORTANT return true if device is down
testUPnP()
{
  return `gssdp-discover -n 10 -t uuid:$1 | grep "resource available" | wc -l`
}

HFILE='/var/www/monitoring/device_upnp.list'
DBFILE='/var/www/judo.db'

if [[ -z $HFILE ]]
then
  echo "$HFILE doesn't exist"
  exit 1
fi

while read line
do
  uuid=$(echo "$line" | cut -d, -f5)
  mac=$(echo "$line" | cut -d, -f4)
  new_polls=1
  prev_status_polls=$(sqlite3 $DBFILE "SELECT cur_state,polls_in_state FROM dev_status WHERE mac = '$mac';")
  prev_status=$(echo "$prev_status_polls" | cut -d\| -f1)
  prev_polls=$(echo "$prev_status_polls" | cut -d\| -f2)

  #remeber testUPnP returns true if device is down
  if `testUPnP $uuid`
  then
    new_status="Down"
    if [ "$prev_status" == "Up" ]
    then
      sqlite3 $DBFILE "UPDATE devices SET ip = NULL, upnp_port = NULL WHERE mac = '$mac';"
    fi
  else
    new_status="Up"
    if [ "$prev_status" == "Down" ]
    then
      location=`gssdp-discover -n 10 -t uuid:$uuid | grep "Location:" | sed 's/ *Location: http:\/\///g'| cut -d/ -f1 | sed 's/\///g'`
      echo "$location"
      ip=$(echo "$location" | cut -d: -f1)
      upnp_port=$(echo "$location" | cut -d: -f2)
      echo "$ip:$upnp_port"
      sqlite3 $DBFILE "UPDATE devices SET ip = '$ip', upnp_port = '$upnp_port' WHERE mac = '$mac';"
    fi
  fi

  if [ "$prev_status" == "$new_status" ]
  then
    new_polls=$((prev_polls + 1))
  fi
  sqlite3 $DBFILE "UPDATE dev_status SET cur_state = '$new_status', polls_in_state = '$new_polls' WHERE mac = '$mac';"
  echo "$uuid,$mac,$new_status,$prev_status,$new_polls"
done < ${HFILE}

Yamaha HTR-4065


Even though Yamaha have UPnP we will rather use YNC to get the status, because we also want to know which input is actually active. Below is python script which finds the status and input and store that in sqlite database.
#! /usr/bin/python

import sqlite3
import sys
import httplib, urllib
import re
from xml.dom import minidom
from xml.parsers.expat import ExpatError

conn = httplib.HTTPConnection("10.0.0.36:80")
xml_req_power = '<?xml version="1.0" encoding="utf-8"?><YAMAHA_AV cmd="GET"><Main_Zone><Power_Control><Power>GetParam</Power></Power_Control></Main_Zone></YAMAHA_AV>'

xml_req_input = '<?xml version="1.0" encoding="utf-8"?><YAMAHA_AV cmd="GET"><Main_Zone><Input><Input_Sel>GetParam</Input_Sel></Input></Main_Zone></YAMAHA_AV>'

params = urllib.urlencode({'q': 'set'})
headers = { 'Content-Type': 'application/xml', "Content-Length": "%d" % len(xml_req_power)}

conn.request("POST", "/YamahaRemoteControl/ctrl", "", headers)
conn.send(xml_req_power)

xmldoc = minidom.parseString(conn.getresponse().read())
xml_input_element = xmldoc.getElementsByTagName('Power')
#print xml_input_element[0]
for node in xml_input_element:
  power_status = node.firstChild.nodeValue

conn.close()

#=================================
#Geting input

headers = { 'Content-Type': 'application/xml', "Content-Length": "%d" % len(xml_req_input)}

conn.request("POST", "/YamahaRemoteControl/ctrl", "", headers)
conn.send(xml_req_input)
#response = conn.getresponse()

#print response.status, response.reason
#response_data = response.read()

#print response_data

xmldoc = minidom.parse(conn.getresponse())
xml_input_element = xmldoc.getElementsByTagName('Input_Sel')
for node in xml_input_element:
  av_input = node.firstChild.nodeValue

conn.close()

print "Status\t\tAV Input"
print power_status + "\t\t" + av_input

select_query = "SELECT cur_state,polls_in_state,input FROM dev_status WHERE mac = '00:a0:de:92:6b:78';"
#print update_query
new_polls=1
judodb = sqlite3.connect('/var/www/judo.db')
cur = judodb.cursor()
rows = cur.execute(select_query)
for row in rows:
  prev_state = row[0]
  prev_polls = row[1]
  prev_input = row[2]
if prev_state == power_status and prev_input == av_input:
  new_polls = prev_polls + 1
print prev_state + "\t\t" + str(new_polls)
update_query = "UPDATE dev_status SET input = \'" + av_input + "\', cur_state = \'" + power_status + "\', polls_in_state = " + str(new_polls)  + " WHERE mac = '00:a0:de:92:6b:78'"
cur.execute(update_query)
judodb.commit()
judodb.close()

NEC

NEC responds to ping in Standby and of course don't have UPnP, however the NEC remote control over ethernet let us find out the actual status.
Below python script check the status and store it in sqlite database.
#! /usr/bin/python

import sys
import socket
import sqlite3

monitor_ip = '10.0.0.35'
port = 7142
buffer_size = 2048
data_on = '\x01\x30\x41\x30\x41\x30\x36\x02\x30\x31\x44\x36\x03\x74\x0d'


new = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
new.connect((monitor_ip, port))
new.send(data_on)

data = ''

#message is split over several packets so need to get all content, message we expect have 25bytes
packet = new.recv(buffer_size)
data += packet
while len(data) < 25 or not packet:
   packet = new.recv(buffer_size)
   data += packet

new.close()

#Reponse OFF
#00AB120200D60000040004q
#Response ON
#00AB120200D60000040001t

off_response = b'\x30\x30\x41\x42\x31\x32\x30\x32\x30\x30\x44\x36\x30\x30\x30\x30\x30\x34\x30\x30\x30\x34'
on_response = bytes("\x30\x30\x41\x42\x31\x32\x30\x32\x30\x30\x44\x36\x30\x30\x30\x30\x30\x34\x30\x30\x30\x31")


#there seems to be some not printable chars because of packet fragmentation
#using index is dirty hack but don't care
if data[23] == off_response[21]:
  power_status = "Off"
elif data[23] == on_response[21]:
  power_status = "On"
else:
  power_status = "Unknown"

#Now we need to update sqlite with status

select_query = "SELECT cur_state,polls_in_state FROM dev_status WHERE mac = '00:25:5c:2e:36:39';"
#print update_query
new_polls=1

judodb = sqlite3.connect('/var/www/judo.db')
cur = judodb.cursor()
rows = cur.execute(select_query)
for row in rows:
  prev_state = row[0]
  prev_polls = row[1]
if prev_state == power_status:
  new_polls = prev_polls + 1
print prev_state + "\t\t" + str(new_polls)
update_query = "UPDATE dev_status SET cur_state = \'" + power_status + "\', polls_in_state = " + str(new_polls)  + " WHERE mac = '00:25:5c:2e:36:39'"
cur.execute(update_query)
judodb.commit()
judodb.close()


XBOX 360

For monitoring of XBOX 360 I've have to use arping and UPnP, because of limitation of XBOX 360 OS more can be find here including the script.

Automatic Switch Off


Now as we have ways of getting status we just use crontab to run the scripts each 10 minutes and to get the status and store it in to sqlite database. We are storing also how many time we see it consecutively in current status. (polls_in_state column in table below)

sqlite> .schema dev_status
CREATE TABLE dev_status(mac TEXT, cur_state TEXT, polls_in_state NUM, input TEXT);
sqlite> select * from dev_status;
00:25:5c:2e:36:39|On|3|unknown
00:a0:de:92:6b:78|On|3|HDMI1
00:ce:39:b5:48:9e|Up|4|unknown
7c:1e:52:c2:6c:a7|Up|3|unknown
00:01:2e:3a:a3:b2|Down|1214|unknown
Last thing we have to do is set crontab to run script, which check status based on the status of devices finds what is running and if no device coresponding to AV receiver input is running, (means that AV receiver is running for nothing) it will switch it off. Same for NEC.
crontab -l

#Monitoring of home network
*/10 * * * * /var/www/monitoring/judo_icmp_monitoring &> /dev/null
*/10 * * * * /var/www/monitoring/judo_upnp_monitoring &> /dev/null
*/10 * * * * /var/www/monitoring/HTR-4065_State.py &> /dev/null
*/10 * * * * /var/www/monitoring/NECStatus.py &> /dev/null
*/10 * * * * /var/www/monitoring/XBOX360Status.sh &> /dev/null

5,15,25,35,45,55 * * * * /var/www/monitoring/autostop >> /media/nas/public/autostop.log

autostop script
#! /bin/bash

HTR_MAC='00:a0:de:92:6b:78'
NEC_MAC='58:C2:32:97:3A:48'
Evolve_MAC='00:ce:39:b5:48:9e'
XBOX_MAC='7c:1e:52:c2:6c:a7'
Zotac_MAC='00:01:2e:3a:a3:b2'

CGIDIR='/var/www/cgi-bin'
DBFILE='/var/www/judo.db'

getDeviceStatus(){
  SQLQUERY="SELECT cur_state,polls_in_state,input FROM dev_status WHERE mac = '$1';"
  SQLRESULT=`sqlite3 $DBFILE "$SQLQUERY" | sed 's/|/ /g;'`
  echo "$SQLRESULT"
}


ACTION='No action'

echo -n ""

read HTR_STATUS HTR_POLLS_IN_STATUS HTR_INPUT <<< `getDeviceStatus $HTR_MAC`
echo -n "$HTR_STATUS $HTR_POLLS_IN_STATUS $HTR_INPUT"

#Example of output
#00:a0:de:92:6b:78|On|51|HDMI1

if [[ $HTR_STATUS == "On" && $HTR_POLLS_IN_STATUS -gt 3 ]]
then
  case $HTR_INPUT in
    "HDMI1")
       #echo "Check Evolve status"
       read Evolve_STATUS Evolve_POLLS_IN_STATUS Evolve_INPUT <<< `getDeviceStatus $Evolve_MAC`
       echo -n "$Evolve_STATUS $Evolve_POLLS_IN_STATUS $Evolve_INPUT"
       if [[ $Evolve_STATUS == "Down" && $Evolve_POLLS_IN_STATUS -gt 3 ]]
       then
         $CGIDIR/NECOff.py
         $CGIDIR/HTR-4065_Off.py
         ACTION="Switching HTR-4065 and NEC off"
       fi
       ;;
    "HDMI2")
       #echo "Check XBOX 360 status"
       read XBOX_STATUS XBOX_POLLS_IN_STATUS XBOX_INPUT <<< `getDeviceStatus $XBOX_MAC`
       echo -n "$XBOX_STATUS $XBOX_POLLS_IN_STATUS $XBOX_INPUT"
       if [[ $XBOX_STATUS == "Down" && $XBOX_POLLS_IN_STATUS -gt 3 ]]
       then
         $CGIDIR/NECOff.py
         $CGIDIR/HTR-4065_Off.py
         ACTION="Switching HTR-4065 and NEC off"
       fi
       ;;
    "HDMI3")
       #echo "Checking PC Zotac Status"
       read Zotac_STATUS Zotac_POLLS_IN_STATUS Zotac_INPUT <<< `getDeviceStatus $Zotac_MAC`
       echo -n "$Zotac_STATUS $Zotac_POLLS_IN_STATUS $Zotac_INPUT"
       if [[ $Zotac_STATUS == "Down" && $Zotac_POLLS_IN_STATUS -gt 3 ]]
       then
         $CGIDIR/NECOff.py
         $CGIDIR/HTR-4065_Off.py
         ACTION="Switching HTR-4065 and NEC off"
       fi
       ;;
  esac
else
  #AV reciever is Standby for more then hour and NEC is On for
  if [[ ! $HTR_STATUS == "On" ]]
  then
    read NEC_STATUS NEC_POLLS_IN_STATUS NEC_INPUT <<< `getDeviceStatus $NEC_MAC`
    echo -n "$NEC_STATUS $NEC_POLLS_IN_STATUS $NEC_INPUT"

    if [[ $HTR_POLLS_IN_STATE -gt 6 && $NEC_STATUS == "On" && $NEC_POLLS_IN_STATUS -gt 3 ]]
    then
      $CGIDIR/NECoff.py
      ACTION="Switching NEC Off"
    fi
  fi
fi

echo -n "$ACTION"
echo ""
#HTR-4065 HDMI1 : Evolve Down,3 => OFF: NEC, HTR-4065
#HTR-4065 HDMI2 : XBOX_360 Down,3 => OFF: NEC, HTR-4065
#HTR-4065 HDMI3 : PC_Zotac Down,3 => OFF: NEC, HTR-4065
#HTR-4065 HDMI4 : Not Assigned

Wednesday, July 24, 2013

NEC MultiSync Ethernet Remote Control

Two years back I bought NEC MultiSync V321 one of the reasons was it have ethernet port and you can control it over the network.
The other reason is that there is documentation for it provided by NEC which can help you with commands, if you are intelligent and good enough to understand it. Which is probably not my case.
However NEC also offers NEC PD comms tool, which is application which let you control your NEC.
As I have Yamaha AV reciever (for which Yamaha provides control app) my intention recently was to have small app just to switch on/off the monitor and so limit number of remote controlers.
I also recently bought raspberry Pi and find good use for it. I'm running web server there and using cgi I can switch on/off my NEC and also Yamaha (will write about it in future as I will implement more features).
So what I did, I used NEC PD comms tool and wireshark to catch the tcp data payload for on and off, I wrote few lines in python to send those data and add few more to work nicely as cgi.

Here are the source codes:

NECOn.py
#! /usr/bin/python

import socket

monitor_ip = '10.0.0.35'
port = 7142
buffer_size = 1024
data_on = '\x01\x30\x41\x30\x41\x30\x43\x02\x43\x32\x30\x33\x44\x36\x30\x30\x30\x31\x03\x73\x0d'

new = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
new.connect((monitor_ip, port))
new.send(data_on)
recv_data = new.recv(buffer_size)
new.close()

print """
<html>
  <head>
    <meta http-equiv="refresh" content="0;url=http://10.0.0.37" />
    <title>You are going to be redirected</title>
  </head>
  <body>
    Redirecting...
  </body>
</html>
"""

NECOff.py
#! /usr/bin/python

import socket

monitor_ip = '10.0.0.35'
port = 7142
buffer_size = 1024
data_off = '\x01\x30\x41\x30\x41\x30\x43\x02\x43\x32\x30\x33\x44\x36\x30\x30\x30\x34\x03\x76\x0d'


mon_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mon_socket.connect((monitor_ip, port))
mon_socket.send(data_off)
recv_data = mon_socket.recv(buffer_size)
mon_socket.close()

print """
<html>
  <head>
    <meta http-equiv="refresh" content="0;url=http://10.0.0.37" />
    <title>You are going to be redirected</title>
  </head>
  <body>
    Redirecting...
  </body>
</html>
"""
For monitoring purpose I needed to find the howto check status of my NEC and recorded in the Database(I use sqlite3), even the response is just 25 bytes NEC dived it into 2 packets, so we are recording all packets and then check if the On or Off. NECStatus.py
#! /usr/bin/python

import sys
import socket
import sqlite3

monitor_ip = '10.0.0.35'
port = 7142
buffer_size = 2048
data_on = '\x01\x30\x41\x30\x41\x30\x36\x02\x30\x31\x44\x36\x03\x74\x0d'


new = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
new.connect((monitor_ip, port))
new.send(data_on)

data = ''

#message is split over several packets so need to get all content, message we expect have 25bytes
packet = new.recv(buffer_size)
data += packet
while len(data) < 25 or not packet:
   packet = new.recv(buffer_size)
   data += packet

new.close()

#Reponse OFF
#00AB120200D60000040004q
#Response ON
#00AB120200D60000040001t

off_response = b'\x30\x30\x41\x42\x31\x32\x30\x32\x30\x30\x44\x36\x30\x30\x30\x30\x30\x34\x30\x30\x30\x34'
on_response = bytes("\x30\x30\x41\x42\x31\x32\x30\x32\x30\x30\x44\x36\x30\x30\x30\x30\x30\x34\x30\x30\x30\x31")


#there seems to be some not printable chars because of packet fragmentation
#using index is dirty hack but don't care
if data[23] == off_response[21]:
  power_status = "Off"
elif data[23] == on_response[21]:
  power_status = "On"
else:
  power_status = "Unknown"

#Now we need to update sqlite with status

select_query = "SELECT cur_state,polls_in_state FROM dev_status WHERE mac = '00:25:5c:2e:36:39';"
#print update_query
new_polls=1

judodb = sqlite3.connect('/var/www/judo.db')
cur = judodb.cursor()
rows = cur.execute(select_query)
for row in rows:
  prev_state = row[0]
  prev_polls = row[1]
if prev_state == power_status:
  new_polls = prev_polls + 1
print prev_state + "\t\t" + str(new_polls)
update_query = "UPDATE dev_status SET cur_state = \'" + power_status + "\', polls_in_state = " + str(new_polls)  + " WHERE mac = '00:25:5c:2e:36:39'"
cur.execute(update_query)
judodb.commit()
judodb.close()

If you have any questions you can always contact me.