Tuesday, May 7, 2013

Continued Reverse Engineering of the SDE-5001

After posting my previous writeup, I was sort of puzzled by the intricate nature of the authentication protocol for the Samsung SDE-5001.  There are third-party apps such as IP Cam Viewer that appear to login and authenticate correctly (assuming we designate the device as a Night Owl RTSP camera).  Were they simply implementing this proprietary Samsung protocol?

To better understand this issue, I decided to find a way to capture the network traces from a smartphone or a tablet.  To do so, I put my laptop's wireless card into hotspot mode and plugged the laptop into a wired Ethernet connection.  It turns out that Android (including Jelly Bean) devices can't connect to wireless networks placed in this mode, but at least Apple devices can do so.  I use an IPad to establish a WiFi connection to the laptop, allowing me to capture the network traces from the wireless Ethernet card.

By examining the Wireshark traces, it turns out that even though you specify RTSP port 4520, the IP Camera apps connects to port 4524 (+4 offset), which appears to accept authentication requests.

Capturing the network trace you see the following:
OPTIONS rtsp://myipcamera.example.com:4524/1 RTSP/1.0
CSeq: 1
User-Agent: IPCamViewer

When this request is given, the Samsung DVR responds with a 401 Unauthorized, a realm=, and a nonce= value:

RTSP/1.0 401 Unauthorized
CSeq: 1
Date: Wed May  8 06:39:55 2013 GMT
WWW-Authenticate: Digest realm="NET-i", nonce="0000000000000000000000002E923766"
The next sequence includes the Authorization: Digest header.  The realm value and nonce are replayed, and a response= hash is calculated.
OPTIONS rtsp://myipcamera.example.com:4524/1 RTSP/1.0
CSeq: 2
User-Agent: IPCamViewer
Authorization: Digest username="johnsmith", realm="NET-i", nonce="0000000000000000000000007B719AE4", uri="rtsp://myipcamera.example.com:4524/1", response="758b43dd292609c5b6e80084aa688ac5"

How is the response= value calculated?  Aparently it appears to follow the RFC2069 spec:


The documentation at the bottom of http://tools.ietf.org/html/rfc2617 shows how this digest can be calculated in the C language.  The Python equivalent is shown below.  Note that the OPTIONS: is used  since the previous network packet uses it instead of the more typical GET/POST verb requests.
import hashlib
username = "johnsmith"
realm="NET-i"
password="123456"
uri="rtsp://myipcamera.example.com:4524/1"
nonce="0000000000000000000000002E923766"

ha1 =  hashlib.md5("%s:%s:%s" % (username, realm, password)).hexdigest()
ha2 = hashlib.md5("OPTIONS:%s" % uri).hexdigest()

response = hashlib.md5("%s:%s:%s" % (ha1, nonce, ha2)).hexdigest()
print response
I was able to confirm the hash value matched the one the Samsung device transmitted with this Python code. Therefore, it seems as if the Samsung device has both a proprietary way in its ActiveX control to authenticate and a more standards based to do so.

Monday, May 6, 2013

Reverse Engineering the Samsung SDE-5001's authentication protocol...

One of the major limitations in many of Samsung's IP cameras including the SDE-5001, is that they depend on Internet Explorer and ActiveX plugins to view video streams over the web. You can use the dedicated mobile apps from the
respective iPhone and Android app stores, but what if you wanted to use other browsers or clients? Since the documentation mentions the use of the Real Time Streaming Protocol (RTSP), couldn't any compatible client work?

This question prompted me to verify whether the camera could actually followed the RTP/RTSP specification (http://www.ietf.org/rfc/rfc2326.txt).  According to the RFC documents, RTSP normally uses a similar type of authentication flow as Basic HTTP authentication requests. An Authorization: line is added with the username and password base64-encoded in the request header.

Sifting through documents of different Samsung DVR models publicly available, I tried many different variations of the RTSP protocol to try to elicit a response from the Samsung device:

telnet 192.168.0.25 4520

DESCRIBE rtsp://192.168.0.25/profile3/media.smp RTSP/1.0
Authorization: Basic [base64-encoded username]:[base64-decoded pw])
CSeq:1

OPTIONS rtsp://192.168.0.25/profile3/media.smp RTSP/1.0
CSeq: 1

DESCRIBE rtsp://10.240.56.200/mpeg4/media.smp RTSP/1.0
CSeq: 1
Accept: application/sdp
Bandwidth: 384000
Accept-Language: de-DE
User-Agent: QuickTime/7.4.5 (qtver=7.4.5;os=Windows NT 5.1Service Pack 3)

None of these commands would cause the server to reply. This prompted me to check to see if the Samsung device was also using a proprietary protocol to authenticate. Using Wireshark to capture the network traces while attempting to
login successfully to the DVR through Internet Explorer, I found one packet of interest:



The initial network frames exchanged in particular interested me because you can see the plaintext 'AUTHENTICATE' in the initial data payload.  The packet also contains the ID: and PW: (not shown) and then traces of AUTHENTICATE_OK in the subsequent packet responses.  This protocol is consistent with an online Samsung document called
"DVR Protocol Specification for NET-i (and WebViewer)" -- there are Part I and Part II specs. Part I deals with the authentication aspect.  The packet structure is shown below:


The response packet according to the documentation matches the payload being sent for the SDE-5001 devices.  I could see that S & V were the first two characters and AUTHENTICATE being in the middle of the packet. To help confirm that this packet would elicit a reply, I saved the data payload from Wireshark to a file called "authenticate_packet" and wrote this small Python script to see whether I could replay the authentication sequence:

import telnetlib

tn = telnetlib.Telnet("192.168.0.25", 4521)
data = open('authenticate_packet', 'r').read()
tn.write(data)
sess_op = tn.read_some()
print sess_op

Instead of no response from the Samsung device, the response from the Samsung device
all of a sudden yielded:

00000000 53 56 36 41 55 54 48 45 4e 54 49 43 41 54 45 5f |SV6AUTHENTICATE_|
00000010 4f 4b 01 54 45 58 54 53 55 43 43 45 53 0a |OK.TEXTSUCCES.|
0000001e

This experiment helped confirm that the Samsung SDE-5001 follows the same protocol as the Samsung DVR spec. However, the documentation also seemed to suggest that the username and password (ID/PASSWORD) were sent as cleartext.  Decoding the authentication packet, however, yielded a bunch of encoded text.  For instance, for the username "joesmith" and password "123456", the corresponding data was "GtxTGa3KAcI=" and "7c4a8d09ca3762af61e59520943dc26494f8941b".

Understanding how the ID and PASSWORD strings required some extra reverse engineering with the Samsung web interface and the ActiveX modules that get dynamically loaded in the browser. One thing to notice in the JavaScript
of the Samsung web interface that it embeds an <IFRAME> tag and attempts to log the user in with two functions. The data_parser() JavaScript code appears to encode data into a base64, while the second function hex_func_five() appears
to be the MD5 hash of the password.

document.login_page_submit.data1.value = data_parser(document.login_page.data1.value);
document.login_page_submit.data2.value = hex_func_five(document.login_page.data2.value);

The data_parser() function appears to base64-encode the username, but the string doesn't match
the username string "GtxTGa3KAcI=" sent in the Wireshark network packet.   Therefore, it appeared that the Samsung RTSP port was using some additional type of encoding that could only be studied by examining the ActiveX code.

When watching a successful authentication to occur, I noticed that Samsung's web
server renders a page that injects <object> tag into the DOM. This <object> tag is the ActiveX module.

The name of the file is webviewer.cab:
<script language="javascript">
writeObjCtrl(720, 576, 'obj', 'GTileContainerCtlDetroit', 'CLSID:26E1BEAF-C1A1-482b-8714-08844F1BCF7F', '../webviewer.cab#version=1,0,0,276');
</script>

This CAB file can actually be downloaded from the system and extracted using the cabextract utility in Ubuntu:

wget http://192.0.0.25/webviewer.cab

$ cabextract webviewer.cab
webviewer.cab: WARNING; possible 7208 extra bytes at end of file.
Extracting cabinet: webviewer.cab
  extracting H264GTileContainer.ocx
  extracting H264GCalendarCtrl.ocx
  extracting H264GTimeLineCtrl.ocx
  extracting H264GSetupBrowser.ocx
  extracting H264GMediaPlayer.ocx
  extracting H264GMediaSource.ax
  extracting H264GVideoDecoder.ax
  extracting H264GAudioDecoder.ax
  extracting H264GAudioDecoderADPCM.ax
  extracting H264GVideoRenderer.ax
  extracting XNS_h264.dll
  extracting H264MediaMan.dll
  extracting XRegistry.ocx
  extracting device_shr_h264.dll
  extracting h264decoder.dll
  extracting lib_VoiceEngine_dll.dll
  extracting webviewer.inf

The files that were extracted include OCX and DLL files, which are all Portable Executable (PE) files that can be loaded and run in Microsoft Windows. The ActiveX modules that are loaded by Internet Explorer to provide the GUI and controls for the Samsung SmartViewer software. The additional H.264-related modules appear to perform the audio/video decoding too.

Which of the modules is responsible for handling the initial authentication? Doing a search of the string exports with PEBrowser in the modules or simply a grep for the string for "AUTHENTICATE_OK" reveals that this code is actually contained in the file, device_shr_h264.dll.

grep AUTHENTICATE_OK *
Binary file device_shr_h264.dll matches

The interactive debuggers Olly Dbg or IDA Pro (32-bit mode) can be used to try to understand what's happening. We can attach a process to Internet Explorer, login to the Samsung interface with a known username and password, and view the device_shr_h264.dll loaded in memory (listed as device~1.dll). We view the exported strings in the file and trace where the strings are used inside the code:


We can search for "AUTHENTICATE" and find the matching strings:



We can trace into this function and notice that the sprintf() command for the username and password is being generated:


A breakpoint can be added further upstream until you notice that the username and password are being passed into registers.  OllyDbg will show these values in the registers window, so it's easy enough to see when they are referenced.


The exact location in which the user and password can reloading the web page and setting breakpoints until the appropriate sections can be isolated.  I noticed that the username started to show up as a register location where the use of the ASCII string "VSS_SVP2.0" gets referenced.


In the following section of the assembly code, the byte referencing the address location DS:[ESI+10027BDC] is moved into the EDI register, XOR'd with the previous character (the EDL register is cleared to 0x00 earlier), and repeated until the end of the string is reached.  This latter operation is done by using the SCAS opcode is used to increment the ESI register until what is stored in the AL register, 0x00, which is essentially implementing the strlen() function).


The equivalent code for the above assembly code in Python would essentially be:
constant = "VSS_SVP2.0"

init_val = 0x00

for c in constant:
    init_val ^= ord(c)

There is additional code that attempts to XOR this value with other bytes referenced by 0x69A7BE8, but inspecting the memory contents using OllyDbg's Follow In Dump -> Immediate constant reveals that all the data is 0x00, leaving the result of the previous code to be unchanged.  This section therefore seems to be a placeholder for more XOR mixing, but seems to be unused.

In the next section of the code, we observe that a memory address location 0x069A9880 is referenced.     We notice that this address location is stored in the EDI register and the byte is XOR'd with the username. Essentially we have found the key used to perform the rest of the XOR encryption.

The key at address 0x69A9880 appears to start with the data 0x52, 0x50, 0x70, etc..   The maximum amount of space allocated to the username and password appears to be 32-bytes each, so the XOR operation seems to be limited to a 32-byte operation.

The above section of the assembly code is equivalent to the following Python code. Since many of the assembly instructions are performing byte-level operations, we often need to take the results of the AND/XOR operation and mask
the bits with the 0xFF byte. Assuming the secret key is 64-bytes, the XOR algorithm is therefore listed as follows:

def samsung_xor_encode(username, init_val):
  secret_key = [0x52, 0x50, 0x7, 0x15, 0x23, 0x33, 0x41, 0x49, 0x50, 0x53, 0x61, 0x00, 0x08, 0x16, 0x24, 0x26,
       0x34, 0x42, 0x10, 0x28, 0x36, 0x44, 0x63, 0x56, 0x03, 0x11, 0x19, 0x29, 0x37, 0x45, 0x33, 0x56,
       0x57, 0x04, 0x12, 0x20, 0x30, 0x38, 0x46, 0x58, 0x05, 0x13, 0x21, 0x31, 0x39, 0x47, 0x62, 0x06,
       0x14, 0x22, 0x32, 0x40, 0x48, 0x08, 0x37, 0x18, 0x34, 0x26, 0x12, 0x38, 0x26, 0x58, 0x45, 0x08]

  val = ""
  for n, u in enumerate(username):
     result = (init_val ^ ord(u))
     val += chr(result)
     cl = (~init_val & 0xFF)
     cl = cl >> 7
     init_val = init_val << 1
     cl = (cl + init_val) & 0xFF
     init_val = cl ^ secret_key[n]

  return val

We can then use this function to implement the XOR encryption:

init_val = 0x00

for c in constant:
  init_val ^= ord(c)

encoded = samsung_xor_encode("johnsmith", init_val)
decoded = samsung_xor_encode(encoded, init_val)

print "Encoded username: %s" % (encoded.encode('base64'))
print "Decoded username: %s" % (decoded)

The  XOR algorithm can be verified by using known plaintext usernames and observing the authentication packets between the ActiveX module and the Samsung DVR.   Since the XOR algorithm is used for both decryption and encryption, we can verify both by encoding the plaintext username as well as decoding the encrypted username. Fortunately, the password doesn't require this extensive reverse-engineering, since it only computes a standard MD5 hash against the password for authentication.

One important point that should be noted is that this information in this article details an encryption algorithm only used for authentication and not used for any type of copy protection.   The intention of this writeup is to document my understanding of the authentication protocols for these DVR systems and to enable more browser-friendly plug-ins beyond Internet Explorer to work with these systems, especially since Samsung uses custom but publicly disclosed RTP/RTSP protocols to request video/audio streams once the authentication sequence is completed.  If you're interested in doing so, please let me know!

GitHub's web hooks

Recently GitHub has made changes to their web hooks...

1. Your Webhooks need to be URL-encoded now.  In the past, you could get away with it and GitHub would handle the url-encoding themselves.   No longer.

2. All webhooks are now by default monitoring/tracking only push events.  If you have other events, you need to use the GitHub hooks API.

The best way to make these changes?  Use Chrome's Dev HTTP client: https://chrome.google.com/webstore/detail/dev-http-client/aejoelaoggembcahagimdiliamlcdmfm?hl=en

a. Add a Header with Authorization: token <token>where <token> represents your OAuth token for GitHub.

b. Change to use https://

c. Set to api.github.com/repos/<your GitHub owner>/<repo name>/hooks

d. Grab the webhook ID in the JSON response.

c. Set the JSON payload to be add_events and push like the following:

Friday, April 19, 2013

RequireJS

The RequireJS documentation appears to suggest that you can define configurations such as the following:

requirejs.config({baseUrl: 'http://app.example.com',
                  paths: { underscore : 'static/js/external/underscore.js',
                           backbone : 'static/js/external/backbone.js'})

If you try to use this approach, you may find RequireJS appending an extra .js file.

RequireJS also assumes by default that all dependencies are scripts, so it does not expect to see a trailing ".js" suffix on module IDs. RequireJS will automatically add it when translating the module ID to a path. With the paths config, you can set up locations of a group of scripts. All of these capabilities allow you to use smaller strings for scripts as compared to traditional <script> tags.

There may be times when you do want to reference a script directly and not conform to the "baseUrl + paths" rules for finding it. If a module ID has one of the following characteristics  the ID will not be passed through the "baseUrl + paths" configuration, and just be treated like a regular URL that is relative to the document:
  • Ends in ".js".
  • Starts with a "/".
  • Contains an URL protocol, like "http:" or "https:".
Within the RequireJS code, there is a test for the question mark (?):
url += (ext || (/\?/.test(url) ? '' : '.js'));

...to avoid RequireJS add an extra .js to the end, you can do the following to avoid the addtion.JS:

requirejs.config({baseUrl: 'http://app.example.com',
                  paths: { underscore : 'static/js/external/underscore.js?r=1',
                           backbone : 'static/js/external/backbone.js?r=1'})


This pull-request still seems to fix this issue:

https://github.com/jrburke/requirejs/pull/716

Tuesday, April 9, 2013

mailto: links and plus encoding

Python has urllib.quote() and urllib.quote_plus().  Which one should use for URL-encoding mailto: links?  Answer: use urlib.quote(), since spaces will normally be converted to urllib.quote_plus() and cause confusion in whether it actually is part of a person's email address.

http://tools.ietf.org/html/rfc6068#page-8

When creating 'mailto' URIs, any reserved characters that are used in
the URIs MUST be encoded so that properly written URI interpreters
can read them. Also, client software that reads URIs MUST decode
strings before creating the mail message so that the mail message
appears in a form that the recipient software will understand. These
strings SHOULD be decoded before showing the message to the sending
user.

Software creating 'mailto' URIs likewise has to be careful to encode
any reserved characters that are used. HTML forms are one kind of
software that creates 'mailto' URIs. Current implementations encode
a space as '+', but this creates problems because such a '+' standing
for a space cannot be distinguished from a real '+' in a 'mailto'
URI. When producing 'mailto' URIs, all spaces SHOULD be encoded as
%20, and '+' characters MAY be encoded as %2B. Please note that '+'
characters are frequently used as part of an email address to
indicate a subaddress, as for example in .

The 'mailto' URI scheme is limited in that it does not provide for
substitution of variables. Thus, it is impossible to create a
'mailto' URI that includes a user's email address in the message
body. This limitation also prevents 'mailto' URIs that are signed
with public keys and other such variable information.

Monday, April 8, 2013

Using Jekyll and iFrame's

It turns out the use of your Markdown converter affects whether you can embed YouTube clips (i.e. iFrame's).  For this reason, I switched to using rdiscount instead of the default one included in Jekyll:

https://github.com/mojombo/jekyll/issues/232

More context here:

https://github.com/mojombo/jekyll/wiki/install