Ricoh provides a “Basic app” for Windows and Mac that will update the Theta S’ firmware and stitch the dual-fisheye video output into equirectangular video. The application UI and glue code, SphericalViewer.swf
, is built using Adobe Flex, while the video stitching portion, camera communications portion, and firmware updater are native code. The video stitcher is built using OpenCV and FFmpeg. Although reverse engineering native code is rather involved, reverse engineering Flash objects is much easier. Running SphericalViewer.swf
through the JPEXS flash decompiler produced fairly readable output.
The most interesting part was related to the configuration file, CommonConfig.dat
. After opening the file in a text editor, it was immediately obvious that it was Base64-encoded, but the decoded output was gobbledygook. However, I was able to decode it by analyzing the appropriate function in the decompiled Flash object. Indeed, the first step was to Base64-decode the file’s contents. The next step explains why the decoded output didn’t make senseāit was encrypted. After the Base64-decode, the result needs to be twice decrypted using an 256-bit AES key in Cipher Block Chaining (CBC) mode. This would normally prove to be extremely difficult, but the encryption key is defined as a variable in the same function, which makes it quite easy. This allowed me to write a configuration file decoder in Python. Once decoded, the configuration file is just an XML file. I’m not sure what the point of encrypting the configuration file is when the encryption key is easily accessible, and I’m really not sure what the point of encrypting it twice is, particularly with the same key.
<CommonConfig>
<domainInfos>
<domainInfo domainCode="staging">
<serverUri url="https://api.theta360stage.com"/>
<browserUri url="https://theta360stage.com"/>
<updateConfirmSiteUri url="https://staging-hydrangea-statics.s3.amazonaws.com/app/viewer/update_staging.xml"/>
<firmUpdate baseUrl="https://staging-hydrangea-statics.s3.amazonaws.com/app/firmUpdate" fileName="firmUpdateRoot.xml" fileNameM15="firmUpdateRoot_m15.xml" fileNameM20="firmUpdateRoot_m20.xml" />
</domainInfo>
<domainInfo domainCode="integration">
<serverUri url="https://api.osaroid.info"/>
<browserUri url="https://www.osaroid.info"/>
<updateConfirmSiteUri url="https://hydrangea-statics.s3.amazonaws.com/app/viewer/update_integration.xml"/>
<firmUpdate baseUrl="https://hydrangea-statics.s3.amazonaws.com/app/firmUpdate" fileName="firmUpdateRoot.xml" fileNameM15="firmUpdateRoot_m15.xml" fileNameM20="firmUpdateRoot_m20.xml" />
</domainInfo>
<domainInfo domainCode="kuma">
<serverUri url="https://api.kuma.osaroid.info"/>
<browserUri url="https://kuma.osaroid.info"/>
<updateConfirmSiteUri url="NO_SETTING"/>
<firmUpdate baseUrl="NO_SETTING" fileName="firmUpdateRoot.xml" fileNameM15="firmUpdateRoot_m15.xml" fileNameM20="firmUpdateRoot_m20.xml" />
</domainInfo>
<domainInfo domainCode="kan">
<serverUri url="https://api.apekan.osaroid.info"/>
<browserUri url="https://apekan.osaroid.info"/>
<updateConfirmSiteUri url="NO_SETTING"/>
<firmUpdate baseUrl="NO_SETTING" fileName="firmUpdateRoot.xml" fileNameM15="firmUpdateRoot_m15.xml" fileNameM20="firmUpdateRoot_m20.xml" />
</domainInfo>
<domainInfo domainCode="m15">
<serverUri url="https://api.m15.pacpac.theta360devel.com"/>
<browserUri url="https://m15.pacpac.theta360devel.com"/>
<updateConfirmSiteUri url="NO_SETTING"/>
<firmUpdate baseUrl="NO_SETTING" fileName="firmUpdateRoot.xml" fileNameM15="firmUpdateRoot_m15.xml" fileNameM20="firmUpdateRoot_m20.xml" />
</domainInfo>
<domainInfo domainCode="production">
<serverUri url="https://api.theta360.com"/>
<browserUri url="https://theta360.com"/>
<updateConfirmSiteUri url="https://theta360-statics.s3.amazonaws.com/app/viewer/update_production.xml"/>
<firmUpdate baseUrl="https://theta360-statics.s3.amazonaws.com/app/firmUpdate" fileName="firmUpdateRoot.xml" fileNameM15="firmUpdateRoot_m15.xml" fileNameM20="firmUpdateRoot_m20.xml" />
</domainInfo>
</domainInfos>
</CommonConfig>
The configuration file contains a series of different update servers, although only the production server, which is hosted using Amazon S3, seems to work. Upon downloading the XML files, the first thing we learn is the internal names for the different Theta cameras: the Theta S is the m20
; the Theta M15 is, obviously, the m15
; and the original Theta is the m10
. We also find the firmware download URLs and, by testing URL permutations, are able to determine and download all released firmware versions: 1.21, 1.30, 1.42, 1.50, and 1.62 for the Theta S. Here are example URLs for the Theta S root update information file, firmware information file, and the firmware itself.
Opening the firmware in a hex editor shows that it is identified with UNITY FILE V1.10 / RICOH COMPANY
at the beginning of the file. This appears to be the same format use by Ricoh for other camera firmware, but I couldn’t find any information about it. Running the firmware through Binwalk shows that it contains ARM instructions, and an entropy analysis shows that it is not encrypted. I didn’t make any progress besides this.
Maybe I can help you: I read your post and I get really interested to decode the firmware: looking at the data seems obvious that it’s neither (block) encrypted neither compressed since there are large blocks with constant data; I lose my mind for a day and looking at github I found a repository where a guy smarter than me sorted this out: roughly speaking the firmware is xored 0x200 bytes at times with a key of four bytes (the bytes that repeat along the block probably).
The repository is here https://github.com/trolldbois/theta/blob/master/ThetaS-firmware-opening.ipynb.
I had tried to find previous work on Ricoh’s firmware but was unsuccessful. That repository looks quite interesting, but, sadly, there’s no documentation, and I’m getting errors when I try to run some of the notebooks.
Indeed some files are not updated but I tried ThetaS-firmware-opening.ipynb (changing the firmware filename) and I obtained a new file with a lot of readable strings like @0x0011d1d4
TOPPERS/ASP Kernel Release %d.%X.%d for CRYSTAL(ARM946) (Jul 4 2016, 09:49:25)
Copyright (C) 2000-2003 by Embedded and Real-Time Systems Laboratory
Toyohashi Univ. of Technology, JAPAN
Copyright (C) 2004-2014 by Embedded and Real-Time Systems Laboratory
Graduate School of Information Science, Nagoya Univ., JAPAN
The algorithm is not perfect, some strings have the case flipped, An xml file exists at 0x2ce85a but after some tags become garbage.
Found my decoder here; will normally works will all Ricoh firmware.
https://github.com/crypto512/ricohdec