{"id":103530,"date":"2020-08-05T21:01:06","date_gmt":"2020-08-06T04:01:06","guid":{"rendered":"\/blogs\/?p=103530"},"modified":"2024-07-07T23:23:00","modified_gmt":"2024-07-08T06:23:00","slug":"call-an-exorcist-my-robots-possessed","status":"publish","type":"post","link":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/","title":{"rendered":"Call an Exorcist! My Robot\u2019s Possessed!"},"content":{"rendered":"<h2><a name=\"Toc45545095\"><\/a><span lang=\"EN-US\">Overview<\/span><\/h2>\n<p>As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat Research (ATR) recently investigated <em>temi<\/em>, a teleconference robot produced by Robotemi Global Ltd. Our research led us to discover four separate vulnerabilities in the temi robot, which this paper will describe in great detail. These include:<\/p>\n<ol>\n<li>CVE-2020-16170 \u2013 Use of Hard-Coded Credentials<\/li>\n<li>CVE-2020-16168 \u2013 Origin Validation Error<\/li>\n<li>CVE-2020-16167 \u2013 Missing Authentication for Critical Function<\/li>\n<li>CVE-2020-16169 \u2013 Authentication Bypass Using an Alternate Path of Channel<\/li>\n<\/ol>\n<p>Together, these vulnerabilities could be used by a malicious actor to spy on temi\u2019s video calls, intercept calls intended for another user, and even remotely operate temi \u2013 all with zero authentication.<\/p>\n<p>Per McAfee\u2019s vulnerability disclosure policy, we reported our findings to Robotemi Global Ltd. on March 5, 2020. Shortly thereafter, they responded and began an ongoing dialogue with ATR while they worked to adopt the mitigations we outlined in our disclosure report. As of July 15, 2020, these vulnerabilities have been successfully patched\u00a0\u2013 mitigated in version 120 of the temi&#8217;s Robox OS and all versions after 1.3.7931 of the temi Android app. We commend Robotemi for their prompt response and willingness to collaborate throughout this process. We\u2019d go so far as to say this has been one of the most responsive, proactive, and efficient vendors McAfee has had the pleasure of working with.<\/p>\n<p>This paper is intended as a long-form technical analysis of the vulnerability discovery process, the exploits made possible by the vulns, and the potential impact such exploits may have. Those interested in a higher-level, less technical overview of these findings should refer to our summary blog post <a href=\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/robot-character-analysis-reveals-trust-issues\/\">here<\/a>.<\/p>\n<h2><a name=\"Toc45545096\"><\/a>Contents<\/h2>\n<p><a href=\"#Toc45545095\">Overview<\/a><br \/>\n<a href=\"#Toc45545096\">Contents<\/a><br \/>\n<a href=\"#Toc45545097\">What is temi<\/a><br \/>\n<a href=\"#Toc45545098\">Normal Operation of temi<\/a><br \/>\n<a href=\"#Toc45545099\">Initial Recon<\/a><br \/>\n<a href=\"#Toc45545100\">Port Scanning<\/a><br \/>\n<a href=\"#Toc45545101\">Capturing Traffic<\/a><br \/>\n<a href=\"#Toc45545102\">Getting a Shell<\/a><br \/>\n<a href=\"#Toc45545103\">Finding temi\u2019s Code<\/a><br \/>\n<a href=\"#Toc45545104\">Reversing the Code for Video Calls<\/a><br \/>\n<a href=\"#Toc45545105\">Brute-Forcing the Channel Name as an Attack Vector<\/a><br \/>\n<a href=\"#Toc45545106\">Exploring MQTT Attack Vectors<\/a><br \/>\n<a href=\"#Toc45545107\">Overview<\/a><br \/>\n<a href=\"#Toc45545108\">Modifying the temi Phone App<\/a><br \/>\n<a href=\"#Toc45545109\">The Relationship Between Robot IDs and MQTT Topics<\/a><br \/>\n<a href=\"#Toc45545110\">How are MQTT Call Invite Messages Published?<\/a><br \/>\n<a href=\"#Toc45545111\">Intercepting Calls<\/a><br \/>\n<a href=\"#Toc45545112\">Problem: temi Won\u2019t Answer My Calls<\/a><br \/>\n<a href=\"#Toc45545113\">Solution: Become an OWNER<\/a><br \/>\n<a href=\"#Toc45545114\">Adding an OWNER: Phone App\u2019s Perspective<\/a><br \/>\n<a href=\"#Toc45545115\">Adding an OWNER: temi\u2019s Perspective<\/a><br \/>\n<a href=\"#Toc45545116\">Detour: Sneaking Onto temi\u2019s Contact List<\/a><br \/>\n<a href=\"#Toc45545117\">Gaining OWNER Privileges<\/a><br \/>\n<a href=\"#Toc45545118\">Refining the Exploits<\/a><br \/>\n<a href=\"#Toc45545119\">Impact<\/a><br \/>\n<a href=\"#Toc45545120\">Conclusion<\/a><\/p>\n<h2><a name=\"Toc45545097\"><\/a>What is temi?<\/h2>\n<p>Robots.<\/p>\n<p>The final frontier.<\/p>\n<p>For an Android tablet \u2018brain\u2019 sitting atop a 4-foot-tall robot, temi packs a lot of sensors into a small form factor. These include 360\u00b0 LIDAR, three different cameras, five proximity sensors, and even an Inertial Measurement Unit \u00a0(IMU) sensor, which is a sort of accelerometer + gyroscope + magnetometer all-in-one. All these work together to give temi something close to the ability to move autonomously through a space while avoiding any obstacles. If it weren\u2019t for the nefarious forces of stairs and curbs, temi would be unstoppable.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-103533 size-full\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/temi.jpg\" alt=\"\" width=\"1920\" height=\"1080\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/temi.jpg 1920w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/temi-300x169.jpg 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/temi-1024x576.jpg 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/temi-768x432.jpg 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/temi-1536x864.jpg 1536w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/temi-205x115.jpg 205w\" sizes=\"auto, (max-width: 1920px) 100vw, 1920px\" \/><\/p>\n<p>Robotemi markets its robot as being used primarily for teleconferencing. Articles linked from the temi website describe the robot\u2019s applications in various industries: Connected Living recently partnered with temi for use in elder care, the Kellog\u2019s caf\u00e9 in NYC adopted temi to \u201cenhance the retail experience\u201d, and corporate staffing company Collabera uses temi to \u201cimprove cross-office communication.\u201d Despite its slogan of \u201cpersonal robot\u201d, it appears that temi is designed for both consumer <em>and<\/em> enterprise applications, and it\u2019s the latter that really got us at McAfee Advanced Threat Research interested in it as a research target. Its growing presence in the medical space, which temi\u2019s creators have accommodated by stepping up production to 1,000 units a month, is especially interesting given the greatly increased demand for remote doctor\u2019s visits. What would a compromised temi mean for its users, whether it be the mother out on business, or the patient being diagnosed via robotic proxy? We placed our preorder and set out to find out.<\/p>\n<h2><a name=\"Toc45545098\"><\/a>Normal Operation of temi<\/h2>\n<p>Once it finally arrived, we got to setting it up the way any user might: we unboxed it, plugged in its charging dock, and connected it to WiFi. Normal operation of the temi robot is done through the use of its smart phone app, and at first startup temi prompted us to scan a QR code with the app. The phone used to scan the QR code becomes temi\u2019s \u201cadmin\u201d, allowing you to control the robot remotely by simply calling it. Temi can have many contacts outside of its singular admin, and becoming a contact is fairly straightforward. Whenever you launch the temi phone app, it scans your phone\u2019s contacts and automatically adds any numbers that have been registered with the temi app to the app\u2019s contact list. If any of those contacts happens to be a temi admin, you can call their temi simply by clicking that contact, as shown in <u>Figure 1<\/u>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103536\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/selecting-a-contact.png\" alt=\"\" width=\"1432\" height=\"755\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/selecting-a-contact.png 1432w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/selecting-a-contact-300x158.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/selecting-a-contact-1024x540.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/selecting-a-contact-768x405.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/selecting-a-contact-205x108.png 205w\" sizes=\"auto, (max-width: 1432px) 100vw, 1432px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 1: Selecting a contact from the temi phone app<\/p>\n<p>In this way, users of the phone app besides temi\u2019s admin can still call a temi robot using this method. Since initiating a call with a temi allows you to remotely operate it in addition to seeing through its camera and hearing through its microphone, giving anyone the ability to call your temi could prove\u2026 problematic. Temi\u2019s creators address this exact issue on their FAQ page, as shown in <u>Figure 2<\/u>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103539\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/connect-to-temi.png\" alt=\"\" width=\"826\" height=\"270\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/connect-to-temi.png 826w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/connect-to-temi-300x98.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/connect-to-temi-768x251.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/connect-to-temi-205x67.png 205w\" sizes=\"auto, (max-width: 826px) 100vw, 826px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 2: &#8220;Can anyone connect to my temi?&#8221;, from the temi&#8217;s FAQ page<\/p>\n<p>The first line here is a bit misleading, since adding a temi&#8217;s admin as a phone contact seems to be sufficient to call that temi \u2013 the admin does not need to also have you added as a phone contact for this to work. That being said, this doesn\u2019t mean that just anyone can start controlling your temi; &#8220;physical permission on the robot side&#8221; is required for calls made by users that aren&#8217;t temi&#8217;s admin or explicitly authorized by said admin. In practice, this corresponds to a call notification on the temi&#8217;s screen that the user can either answer or decline. In other words, it\u2019s no more or less \u201csecure\u201d than receiving cold calls on your cell phone.<\/p>\n<p>As for the last line, which refers to an admin\u2019s ability to grant certain users the option to \u201chop in\u201d to the robot, this is also done through the phone app by simply selecting the \u201cInvite New Member\u201d option from the temi\u2019s contact entry in the app, and then selecting one or more users from the app\u2019s contact list to \u201cinvite\u201d, as shown in <u>Figure 3<\/u>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103542\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/special-permissions.png\" alt=\"\" width=\"1009\" height=\"561\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/special-permissions.png 1009w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/special-permissions-300x167.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/special-permissions-768x427.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/special-permissions-205x114.png 205w\" sizes=\"auto, (max-width: 1009px) 100vw, 1009px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 3: How to grant users special permissions from the phone app<\/p>\n<p>Once a call has been established between the robot and a phone user, which either party may initiate, the phone user can do things like manually drive the robot around, add, remove, and navigate to saved locations, patrol between saved locations, control its volume, and more.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103545\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/driving-temi.jpg\" alt=\"\" width=\"943\" height=\"532\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/driving-temi.jpg 943w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/driving-temi-300x169.jpg 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/driving-temi-768x433.jpg 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/driving-temi-205x116.jpg 205w\" sizes=\"auto, (max-width: 943px) 100vw, 943px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 4: Driving the temi using the phone app<\/p>\n<p>Due to the level of control afforded to temi\u2019s callers, the calling functionality quickly became a priority during our investigation of the temi robot.<\/p>\n<h2><a name=\"Toc45545099\"><\/a>Initial Recon<\/h2>\n<h3><a name=\"Toc45545100\"><\/a>Port Scanning<\/h3>\n<p>While a robot was certainly a bit different from our typical targets, we began our reconnaissance with methods that have stood the test of time: port scans, packet captures, and a local shell.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103548\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/Nmap.png\" alt=\"\" width=\"975\" height=\"363\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Nmap.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Nmap-300x112.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Nmap-768x286.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Nmap-205x76.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 5: Running Nmap on the temi<\/p>\n<p>An Nmap scan revealed only one open port: TCP port 4443. Immediately we knew that Nmap\u2019s classification of this port being used for Pharos, a \u201csecure office printing solution for your business\u201d, was almost certainly wrong. Instead, it was likely that this port was being used for TLS communication as an alternative to the standard 443. While good to know, this didn\u2019t tell us much about the type of traffic temi expected on this port, or even what service(s) was handling that traffic, so we moved on.<\/p>\n<h3><a name=\"Toc45545101\"><\/a>Capturing Traffic<\/h3>\n<p>Next on our checklist was to obtain some captures of the robot\u2019s network traffic, focusing on traffic generated during boot, during an update, and during a video call.<\/p>\n<p>Traffic captured when temi was booting up but otherwise idling showed three unique external IP addresses being accessed: 98.137.246.7, 54.85.186.18, and 34.206.180.208.<\/p>\n<p>Running <a href=\"https:\/\/linux.die.net\/man\/1\/nslookup\">nslookup<\/a> on these IPs revealed that the first pointed to some Yahoo! media server, likely used for its news app, while the other two appeared to be AWS instances.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-104583\" src=\"\/blogs\/wp-content\/uploads\/2020\/08\/nslookup-5.png\" alt=\"\" width=\"975\" height=\"279\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/08\/nslookup-5.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/08\/nslookup-5-300x86.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/08\/nslookup-5-768x220.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/08\/nslookup-5-205x59.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 6: Running nslookup on IPs accessed by temi<\/p>\n<p>As for the data being sent\/received, there wasn\u2019t much to look at. The Yahoo! address was being accessed via HTTP (port 80), but the TCP packets had no payload. As for the AWS addresses, these were being accessed via TLS (port 443) and the data was encrypted.<\/p>\n<p>For updates, temi accessed \u201ctemi-ota-updates.s3-accelerate.amazonaws.com\u201d, which was almost certainly a custom AWS instance set up by the folks at Robotemi. As with the other traffic to AWS, the updates were being encrypted.<\/p>\n<h3><a name=\"Toc45545102\"><\/a>Getting a Shell<\/h3>\n<p>In order to prod deeper, we needed a local shell on our temi. Fortunately, wireless connections via <a href=\"https:\/\/developer.android.com\/studio\/command-line\/adb\">Android Debug Bridge, or ADB<\/a>, could be enabled through temi\u2019s settings from its touchscreen. Better still, the shell provided through ADB had root privileges, although the device itself was not \u201crooted\u201d. This was likely done to help facilitate \u201ctemi Developers\u201d, since the temi website has <a href=\"https:\/\/www.robotemi.com\/developers\/\">an entire portal dedicated to helping users develop custom apps for their robot<\/a>.<\/p>\n<p>Unfortunately, the default shell granted via ADB was fairly stripped down, and each reboot would mean having to manually reopen temi\u2019s ADB port from its touchscreen before being able to connect to it again. Furthermore, this method required the temi software to be running in order to access our shell, which would leave us without recourse if our prodding somehow bricked that software. This was far from ideal.<\/p>\n<p>While commands like <a href=\"https:\/\/developer.android.com\/studio\/command-line\/adb#copyfiles\">adb push<\/a> made moving custom ARM binaries for bash, <a href=\"https:\/\/busybox.net\/\">busybox<\/a>, and ssh onto temi fairly trivial, getting anything to run on boot proved more challenging. For starters, temi did not have the standard \/etc\/init.d\/ directory. Additionally, the primary scripts that would run on boot, like \/init.rc, would be loaded directly from the boot image, meaning any edits made to them would not persist across reboots. This was likely by design as part of Android\u2019s SELinux security model \u2013 we would have to be a little more creative if we wanted to convince temi to start an SSH server on boot.<\/p>\n<p>Digging through the contents of \/init.rc, however, gave us a clue:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103557\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/interesting-entry.png\" alt=\"\" width=\"1090\" height=\"232\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/interesting-entry.png 1090w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/interesting-entry-300x64.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/interesting-entry-1024x218.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/interesting-entry-768x163.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/interesting-entry-205x44.png 205w\" sizes=\"auto, (max-width: 1090px) 100vw, 1090px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 7: Interesting entry in \/init.rc<\/p>\n<p>For those unfamiliar with Android\u2019s Init language,<\/p>\n<p style=\"text-align: left; padding-left: 40px;\">\u201cThe init language is used in plain text files that take the .rc file extension. There are typically multiple of these in multiple locations on the system, described below.<br \/>\n\/init.rc is the primary .rc file and is loaded by the init executable at the beginning of its execution. It is responsible for the initial set up of the system.\u201d \u2013 <a href=\"https:\/\/android.googlesource.com\/platform\/system\/core\/+\/master\/init\/README.md\">Android docs<\/a>.<\/p>\n<p>The entry shown in <u>Figure 7<\/u>, one of the hundreds contained in temi\u2019s init.rc, tells the system to launch the service \u201cflash_recovery\u201d during boot with the argument \u201c\/system\/bin\/install-recovery.sh\u201d. The \u201cclass main\u201d is just a label used to group entries together, and \u201coneshot\u201d just means \u201cdo not restart the service when it exits.\u201d This entry stood out to us because it was invoking a script located in \/system\/bin\/, which was <strong>not<\/strong> loaded from the boot image, meaning any changes should stick. While the \/system partition is read-only by default, our root privileges made it trivial to temporarily remount \/system as read-write in order to add the following line to the end: \u201c\/system\/bin\/sshd\u201d. With that, we had a reliable means of getting a root shell on our temi to explore further.<\/p>\n<h3><a name=\"Toc45545103\"><\/a>Finding temi\u2019s Code<\/h3>\n<p>The first thing we did with our newfound freedom was run netstat:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103560\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/netstat.png\" alt=\"\" width=\"975\" height=\"448\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/netstat.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/netstat-300x138.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/netstat-768x353.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/netstat-205x94.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 8: Running netstat<\/p>\n<p style=\"text-align: left;\">It appeared that most networking, including the open port 4443 we found using Nmap earlier, was being handled by \u201ccom.roboteam.teamy.usa\u201d. Based on the name alone, this was likely the main temi software running on the robot. Additionally, the name looked more like the name of an Android package than a native binary. We confirmed this by running:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103563\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/binary.png\" alt=\"\" width=\"820\" height=\"194\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/binary.png 820w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/binary-300x71.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/binary-768x182.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/binary-205x49.png 205w\" sizes=\"auto, (max-width: 820px) 100vw, 820px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 9: Trying to find the binary for com.roboteam.teamy.usa in \/proc<\/p>\n<p>app_process32 (or app_process64, depending on architecture), is the binary used by Android for running Android apps, meaning we were looking for an APK, not an ELF. Under this assumption, we instead tried to find the code for this process using Android\u2019s <a href=\"http:\/\/adbcommand.com\/adbshell\/pm\">pm<\/a> command:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103566\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/looking-for-the-APK.png\" alt=\"\" width=\"975\" height=\"139\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/looking-for-the-APK.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/looking-for-the-APK-300x43.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/looking-for-the-APK-768x109.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/looking-for-the-APK-205x29.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 10: Looking for the APK for &#8220;com.roboteam.teamy.usa&#8221;<\/p>\n<p>Sure enough, we had our APK.<\/p>\n<p>Typically, every installed app on Android has a corresponding data directory, and we were able to find the one for this package under \/data\/data\/com.roboteam.teamy.usa:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103569\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/data-directory.png\" alt=\"\" width=\"1074\" height=\"194\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/data-directory.png 1074w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/data-directory-300x54.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/data-directory-1024x185.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/data-directory-768x139.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/data-directory-205x37.png 205w\" sizes=\"auto, (max-width: 1074px) 100vw, 1074px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 11: data directory for &#8220;com.roboteam.teamy.usa&#8221;<\/p>\n<p>The lib\/ directory contained native code used by the package:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103572\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/native-code.png\" alt=\"\" width=\"975\" height=\"260\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-code.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-code-300x80.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-code-768x205.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-code-205x55.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 12: native code used by &#8220;com.roboteam.teamy.usa&#8221;<\/p>\n<p>After looking into several of these libraries, libagora-rtc-sdk-jni.so in particular stood out to us since it was part of <a href=\"https:\/\/www.agora.io\/en\/\">Agora<\/a>, a platform that provides SDKs for voice, video, and Real-Time-Messaging (RTM) services. It was likely that temi was using Agora to implement its video calling functionality \u2013 the functionality we were most interested in.<\/p>\n<p>By looking at this binary\u2019s strings, we were also able to determine that temi was using version 2.3.1 of the Agora Video SDK:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103575\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/finding-Agora-SDK.png\" alt=\"\" width=\"975\" height=\"149\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-Agora-SDK.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-Agora-SDK-300x46.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-Agora-SDK-768x117.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-Agora-SDK-205x31.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 13: Finding the Agora SDK version using strings<\/p>\n<p>Of course, we were equally interested in the code for the temi phone app, which we obtained through the use of <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=braveheart.apps.apkextract&amp;hl=en\">APK Extractor<\/a> and ADB. We were also curious to see if the Android phone app used the same version of the Agora SDK, which we checked by comparing their MD5 hashes:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103578\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes.png\" alt=\"\" width=\"1714\" height=\"380\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes.png 1714w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes-300x67.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes-1024x227.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes-768x170.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes-1536x341.png 1536w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/comparing-MD5-hashes-205x45.png 205w\" sizes=\"auto, (max-width: 1714px) 100vw, 1714px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 14: Comparing MD5 hashes for libagora between temi robot and temi phone app<\/p>\n<p>Sure enough, they were the identical.<\/p>\n<h2><a name=\"Toc45545104\"><\/a>Reversing the Code for Video Calls<\/h2>\n<p>The next step was to begin reversing these apps in order to better understand how they worked. We decided to start by looking at the phone app\u2019s code, since it was easier to test the behavior of the phone app compared to the robot.<\/p>\n<p>In order to decompile and analyze the APK, we used <a href=\"https:\/\/github.com\/skylot\/jadx\">JADX<\/a>. Although there are many Java decompilers out there that work on Android code, JADX stood out to us due to its various features:<\/p>\n<ul>\n<li>It can open APK files directly (will convert .dex to .jar and merge multiple .dex files automatically)<\/li>\n<li>It handles modern Java features like nested classes and inline lambda functions better than most other decompilers<\/li>\n<li>For any class being viewed, you can click on the &#8220;smali&#8221; tab to see the smali code<\/li>\n<li>It displays line numbers synchronized with the corresponding .line directives in the bytecode, making it easier to map decompiled Java code back to the original bytecode<\/li>\n<li>You can right-click on any method, member, or class to see its usage or declaration<\/li>\n<\/ul>\n<p>The temi Android phone app, like most non-trivial Android apps, was massive. Instead of groping in the dark and hoping to stumble upon some interesting code, we decided to take a more targeted approach. From the outset, we knew that our focus was on temi\u2019s calling functionality since this would provide the greatest impact for any attacker. We also now knew that temi was using the Agora SDK to implement this calling functionality. After looking through Agora\u2019s Java API Reference for Android (v2.3.1), we decided that the function joinChannel() would be a good place to start our investigation:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103581\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/Agora-documentation.png\" alt=\"\" width=\"937\" height=\"414\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Agora-documentation.png 937w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Agora-documentation-300x133.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Agora-documentation-768x339.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Agora-documentation-205x91.png 205w\" sizes=\"auto, (max-width: 937px) 100vw, 937px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 15: Agora&#8217;s documentation for joinChannel()<\/p>\n<p>By opening libagora-rtc-sdk-jni.so in IDA and looking at its exports, we were able to find a function called nativeJoinChannel() at the offset 0xD4484:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103584\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/libagora-exports.jpg\" alt=\"\" width=\"465\" height=\"170\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/libagora-exports.jpg 465w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/libagora-exports-300x110.jpg 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/libagora-exports-205x75.jpg 205w\" sizes=\"auto, (max-width: 465px) 100vw, 465px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 16: Some of libagora&#8217;s exports<\/p>\n<p>Using JADX\u2019s search feature, we found a function with the same name in the decompiled code, located on line 960 in the class RtcEngineImpl:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103587\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/native-Agora-methods.png\" alt=\"\" width=\"975\" height=\"227\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-Agora-methods.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-Agora-methods-300x70.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-Agora-methods-768x179.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/native-Agora-methods-205x48.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 17: Native Agora methods in RtcEngineImpl<\/p>\n<p>From here, we began the tedious process of working backwards to trace the call chain for video calls all the way to their entry points. We did this by looking for the method that invoked nativeJoinChannel(), then looking for the method that invoked that method, and so on. The return on investment for our tireless efforts was the following:<\/p>\n<p><a href=\"\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-104403 size-large\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-1024x746.png\" alt=\"\" width=\"1024\" height=\"746\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-1024x746.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-300x219.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-768x559.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-1536x1119.png 1536w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-2048x1492.png 2048w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Code_Flow_Diagram_White_BG-177x129.png 177w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p style=\"text-align: center;\">Figure 18: Code flow diagram for the various ways of making video calls using the temi phone app<\/p>\n<p>At a high level, the code for outgoing calls has four entry-points, and these map 1-to-1 with the four ways of initiating a call from the temi phone app:<\/p>\n<ol>\n<li>Select a contact and hit \u201cCall\u201d.\u00a0This will call that contact\u2019s phone directly, not their temi. This corresponds to the \u201cContact Details \u2192 Contact Call\u201d code path outlined in the graph (green region).<\/li>\n<li>Select a contact and, if they have a temi robot, hit \u201cConnect\u201d. This will call their temi and corresponds to the \u201cContact Details \u2192 Robot Call\u201d code path outlined in the graph (orange region).<\/li>\n<li>Go to the app\u2019s \u201cRecents\u201d tab and select one of the contacts\/robots you have recently called or have called you. This will call that contact or robot and corresponds to the \u201cRecent Calls \u2192 Call\u201d code path outlined in the graph (blue region).<\/li>\n<li>If you are an admin, select your temi from beneath the \u201cMy temi\u201d heading on the \u201cContacts\u201d tab and hit the \u201cConnect\u201d button from the following screen, also known as the Robot Details screen. This will call your temi and corresponds to the \u201cRobot Details \u2192 Call\u201d code path outlined in the graph (red region).<\/li>\n<\/ol>\n<p>The classes and methods contained within the colored regions handle rendering the screens from which calls can be initiated and the binding of these buttons to the code that actually handles calling. No matter the entry point, all outgoing calls converge at TelepresenceService.initiateCall(), so it\u2019s worth taking a closer look at this method:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103593\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/TelepresenceService.png\" alt=\"\" width=\"975\" height=\"227\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/TelepresenceService.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/TelepresenceService-300x70.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/TelepresenceService-768x179.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/TelepresenceService-205x48.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 19: TelepresenceService.initiateCall()<\/p>\n<p>And here are the four ways it is invoked:<\/p>\n<p>Contact Details \u2192 Contact Call<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103596\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/contact-call.png\" alt=\"\" width=\"1431\" height=\"191\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/contact-call.png 1431w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/contact-call-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/contact-call-1024x137.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/contact-call-768x103.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/contact-call-205x27.png 205w\" sizes=\"auto, (max-width: 1431px) 100vw, 1431px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 20: Invoking initiateCall() from Contact Details \u2192 Contact Call<\/p>\n<p>Contact Details \u2192 Robot Call<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103599\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/robot-call.png\" alt=\"\" width=\"1431\" height=\"191\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-call.png 1431w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-call-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-call-1024x137.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-call-768x103.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-call-205x27.png 205w\" sizes=\"auto, (max-width: 1431px) 100vw, 1431px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 21: Invoking initiateCall() from Contact Details \u2192 Robot Call<\/p>\n<p>Recent Calls \u2192 Call<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103602\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/recent-calls.png\" alt=\"\" width=\"1431\" height=\"191\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/recent-calls.png 1431w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/recent-calls-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/recent-calls-1024x137.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/recent-calls-768x103.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/recent-calls-205x27.png 205w\" sizes=\"auto, (max-width: 1431px) 100vw, 1431px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 22: Invoking initiateCall() from Recent Calls \u2192 Call<\/p>\n<p>Robot Details \u2192 Call<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103605\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/robot-details.png\" alt=\"\" width=\"1431\" height=\"191\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-details.png 1431w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-details-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-details-1024x137.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-details-768x103.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/robot-details-205x27.png 205w\" sizes=\"auto, (max-width: 1431px) 100vw, 1431px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 23: Invoking initiateCall() from Robot Details \u2192 Call<\/p>\n<p>The first parameter is an ID used to identify the callee. For temi robots, this appears to be something called a \u201crobot ID\u201d, whereas for contacts it is obtained via a call to getContactId(). Interestingly, for recent calls, which can be either a contact <em>or<\/em> a robot, the ID is obtained via a call to getMd5PhoneNumber(), which is a bit strange since temi does not (as far as we can tell) have a phone number. We will expand on this later.<\/p>\n<p>The second parameter is a string denoting the caller\u2019s type. Since we were looking at exclusively phone app code here, we assumed that \u201ctypeUser\u201d denotes a caller using the phone app.<\/p>\n<p>The third parameter is the callee\u2019s display name \u2013 straightforward enough.<\/p>\n<p>The fourth and final parameter is of a custom enum type that denotes the <em>callee\u2019s<\/em> type. If calling a temi, its type is CallContactType.TEMI_CALL; otherwise, it\u2019s CallContactType.CONTACT_CALL. Note that for the Recent Calls entry point, this value is obtained dynamically, which makes sense since this path handles calls to both contacts and temis.<\/p>\n<p>It\u2019s also worth noting that initiateCall() invokes InvitationManager.createInvitation() in order to create an Invitation object which represents the \u201ccall invitation\u201d to be sent to the callee:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103608\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/call-invitation.png\" alt=\"\" width=\"1430\" height=\"128\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/call-invitation.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/call-invitation-300x27.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/call-invitation-1024x92.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/call-invitation-768x69.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/call-invitation-205x18.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 24: InvitationManager.createInvitation()<\/p>\n<p>From there, as seen on line 223 of <u>Figure 19<\/u>, initiateCall() passes the result of Utils.getRandomSessionId() as the first parameter to createInvitation(), which becomes the sessionId:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103611\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/25.png\" alt=\"\" width=\"1430\" height=\"168\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/25.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/25-300x35.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/25-1024x120.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/25-768x90.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/25-205x24.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 25: Utils.getRandomSessionId()<\/p>\n<p>All this is doing is randomly generating an integer between 100,000 and 999,999, inclusive. In other words, the sessionId is always a positive, six-digit decimal number.<\/p>\n<p>By tracing this value all the way down the call chain to Agora\u2019s nativeJoinChannel(), we discovered that this sessionId becomes the \u201cchannelName\u201d parameter described in <u>Figure 15<\/u> \u2013 the unique identifier for the chatroom for that particular call.\u00a0 The propagation of this channel name can be seen in <u>Figure 18<\/u>; the <strong>bolded<\/strong> parameter in each method call contains the channel name.<\/p>\n<h2><a name=\"Toc45545105\"><\/a>Brute-Forcing the Channel Name as an Attack Vector<\/h2>\n<p>So why were we so interested in the Agora channel name? Well, if we look back at <u>Figure 15<\/u>, we can see that it is one of only two fields needed to join a channel since the third and fourth fields are labeled optional. The only other required field is the \u201ctoken\u201d, but looking closer at the documentation reveals that \u201cin most circumstances, the static App ID suffices\u201d, and in those cases the token \u201cis optional and can be set as null\u201d:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103614\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/token-parameter.jpg\" alt=\"\" width=\"882\" height=\"179\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/token-parameter.jpg 882w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/token-parameter-300x61.jpg 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/token-parameter-768x156.jpg 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/token-parameter-205x42.jpg 205w\" sizes=\"auto, (max-width: 882px) 100vw, 882px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 26: Agora documentation for joinChannel&#8217;s &#8220;token&#8221; parameter<\/p>\n<p>Our next question was, does temi use a token or does it use a static App ID? We quickly answered that question by looking at how the temi phone app was calling joinChannel():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103617\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/joinchannel.png\" alt=\"\" width=\"975\" height=\"129\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/joinchannel.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/joinchannel-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/joinchannel-768x102.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/joinchannel-205x27.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 27: Agora&#8217;s joinChannel() API function being called from AgoraEngine.joinChannel()<\/p>\n<p>Sure enough, the token parameter is being set to null.<\/p>\n<p>This was starting to look promising \u2013 if the static App ID and channel name are all that\u2019s needed to join an Agora video call and we already knew that temi\u2019s channel names are restricted to six-digit values, then it might be plausible to join existing temi calls through brute force means. All we would need is this \u201cApp ID\u201d.<\/p>\n<p>Looking back at the Agora docs, we looked for which API functions actually use this App Id. As it turned out, there was only one: RtcEngine.create().<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103620\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/RtcEngine.png\" alt=\"\" width=\"975\" height=\"756\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/RtcEngine.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/RtcEngine-300x233.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/RtcEngine-768x595.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/RtcEngine-166x129.png 166w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 28: Agora documentation for RtcEngine.create()<\/p>\n<p>Put briefly, the App ID is a static value issued to developers that is unique to each project and is used as a sort of namespace, segregating different users of Agora\u2019s servers. This ensures that temi users can only use Agora to call other temi users. Since any temi phone app user should be able to call any temi (or other user), there should be a single App ID shared by all temi robots. We decided to take a look at how temi\u2019s code was invoking RtcEngine.create() to see if we could track down the App ID:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103623\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/App-ID.png\" alt=\"\" width=\"624\" height=\"208\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/App-ID.png 624w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/App-ID-300x100.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/App-ID-205x68.png 205w\" sizes=\"auto, (max-width: 624px) 100vw, 624px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 29: AgoraEngine.ensureRtcEngineReadyLock()<\/p>\n<p>Well, that\u2019s not good.<\/p>\n<p>In our view, this was already a vulnerability, denoted by CVE-2020-16170 and having a CVSS score of 8.2. A dedicated attacker would have no problem iterating over all 900,000 possible channel names. Worse yet, doing so would let the attacker \u201cspray and pray\u201d, allowing them to connect to any ongoing temi call without needing to know anything about their victims.<\/p>\n<p>While we certainly couldn\u2019t test such an exploit against a production server, we did decide to test whether an attacker could join an existing temi call knowing only the App ID and channel name in advance. To facilitate this,\u00a0we used an Android phone to call our temi, making sure to run <a href=\"https:\/\/developer.android.com\/studio\/command-line\/logcat\">logcat<\/a> on the phone before the call started. Doing so, we were able to capture the invitation message containing the channel name (labeled \u201csessionId\u201d) as it was being sent to the <a href=\"https:\/\/square.github.io\/okhttp\/\">OkHttp<\/a> client, which then logged it:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103626\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/finding-the-channel-name.png\" alt=\"\" width=\"975\" height=\"128\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-the-channel-name.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-the-channel-name-300x39.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-the-channel-name-768x101.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/finding-the-channel-name-205x27.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 30: Finding the channel name for the call using logcat<\/p>\n<p>Using the hardcoded App ID and the channel name obtained from the logs we were able to successfully join an ongoing call and get both audio and video from at least one of the other callers, proving that this is a viable attack vector.<\/p>\n<p>Although exploitation of this vulnerability utilizes Agora\u2019s video calling API, its existence is a result of temi\u2019s specific implementation of the SDK. While the decision to hardcode temi\u2019s Agora App ID into the phone app is the root cause of this vulnerability, its impact could also have been substantially mitigated by utilizing a token or by allowing a broader range of channel names. Either of these would have rendered the brute-force attack vector incredibly difficult, if not impossible.<\/p>\n<h2><a name=\"Toc45545106\"><\/a>Exploring MQTT Attack Vectors<\/h2>\n<h3><a name=\"Toc45545107\"><\/a>Overview<\/h3>\n<p>Outside of a brute-force method involving joining existing temi calls by trying every possible channel ID, it would be useful to have a more targeted attack vector. Moreover, while joining a call using the brute-force method would let an attacker spy on users, it would not grant them control of the robot itself \u2013 we wanted a method that would let us do both. Fortunately, this level of control is already available during normal operation of the temi robot + phone app.<\/p>\n<p>Although temi uses Agora to facilitate video calls, notifying users of incoming calls, through ringing or otherwise, is not a feature implemented by Agora. In the case of temi, this functionality is implemented using <a href=\"http:\/\/mqtt.org\/\">MQTT<\/a>. MQTT is a publish\/subscribe (pub\/sub) connectivity protocol designed for &#8220;machine-to-machine (M2M)\/Internet of Things&#8221; communication. In it, communications are categorized into &#8220;topics&#8221; and clients can either &#8220;subscribe&#8221; to these topics to receive all their related messages, &#8220;publish&#8221; their own messages to these topics, or both. These topics are structured into a hierarchy delineated in a manner very similar to UNIX-style filesystem naming schemes, with a forward slash (&#8220;\/&#8221;) separating different topic &#8220;levels&#8221;. Communication between clients is facilitated via a system known as a &#8220;<a href=\"https:\/\/en.wikipedia.org\/wiki\/MQTT#MQTT_broker\">broker<\/a>&#8220;, usually implemented as a remote server, which handles establishing and closing connections, message delivery, and client authentication.<\/p>\n<p>Unfortunately, there were several hurdles we would need to overcome in order to leverage temi&#8217;s calling functionality as an unauthorized &#8220;attacker&#8221;.<\/p>\n<p>Firstly, a targeted attack method requires, at a minimum, a reliable way of uniquely identifying the target. In other words, if you are interested in hacking Bob&#8217;s temi, you need a way to pick out Bob&#8217;s temi from every other temi. A softer but equally important requirement is that this identifier must be one that an attacker could plausibly obtain. The more difficult the identifier is to obtain, the more contrived and unrealistic any attack vectors that rely on this identifier become. As an example, a name or phone number might be a plausible identifier; a social security number, less so.<\/p>\n<p>Reversing temi&#8217;s MQTT implementation in its phone app code had revealed a promising potential identifier. We discovered that each robot has its own MQTT topics that it listens on, which are identified using its MQTT client ID, otherwise known as its robot ID. The robot ID for a temi can be easily obtained through normal operation of the phone app by simply adding one of its users as a phone contact, a method we had described previously. This is because the temi phone app allows the user to call any temi registered to a phone number in the user&#8217;s contacts list, which requires that temi&#8217;s robot ID. If the phone app already has access to this information locally, then it should be hypothetically possible to pull this information from the app.<\/p>\n<p>Second, we needed a way to communicate with temi. If calling is facilitated through the publishing of MQTT messages to certain topics, we needed to find a way to publish our own messages to these same topics. On that note, if all MQTT messages must first pass through the broker, we needed a way to trick the broker into thinking we were a trusted MQTT client and bypass any authentication that might be in place.<\/p>\n<p>One way to overcome this hurdle would be to alter the existing temi phone app. <a href=\"https:\/\/medium.com\/swlh\/reverse-engineering-and-modifying-an-android-game-apk-ctf-c617151b874c\">Modifying third party Android apps<\/a> is a well-known process and would let us leverage the code already present for sending MQTT messages instead of writing this code from scratch. Furthermore, since the phone app requires nothing more than the phone number of one of temi&#8217;s users in order to call it (and thus, publish MQTT messages on its topics), this means that the phone app must have a way of authenticating with the broker. If we could send custom MQTT messages to arbitrary topics from the context of a &#8220;trusted&#8221; phone app, then we likely wouldn&#8217;t need to worry about authentication.<\/p>\n<p>This left us with our third and final hurdle: privilege escalation. Although cold calling a temi robot is not difficult to achieve, it does not grant us the ability to remotely operate the robot on its own. This is because calling temi in this way causes it to ring and requires the call to be accepted on temi&#8217;s end via its touchscreen. Only temi&#8217;s admin, the user who registered it via QR code scan, and privileged users manually selected by this admin may directly control temi without any user interaction on the other end. Thus, we needed to find a way to escalate our privilege so that temi would pick up our calls automatically.<\/p>\n<p>If MQTT is the primary means of communication between temi and its phone app users, and admins can manage the privilege levels of users directly from the phone app, it stands to reason that privilege management is likely performed through MQTT. Thus, if we could alter the phone app to spoof a privilege escalation MQTT message, we could overcome this hurdle, as well.<\/p>\n<h3><a name=\"Toc45545108\"><\/a>Modifying the temi Phone App<\/h3>\n<p>Since our entire plan hinged on us being able to alter the bytecode of the phone app without either breaking the app or preventing it from authenticating with the various remote servers it communicates with, we decided to first confirm that this was even possible. To accomplish this, we used a combination of ADB, <a href=\"https:\/\/ibotpeaches.github.io\/Apktool\/\">Apktool<\/a>, <a href=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/technotes\/tools\/unix\/keytool.html\">Keytool<\/a>, and <a href=\"https:\/\/docs.oracle.com\/javase\/7\/docs\/technotes\/tools\/windows\/jarsigner.html\">Jarsigner<\/a>.<\/p>\n<p>We began by unpacking the APK file for the temi phone app using Apktool:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103629\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/unpacking-the-temi-phone-app.png\" alt=\"\" width=\"1430\" height=\"87\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/unpacking-the-temi-phone-app.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/unpacking-the-temi-phone-app-300x18.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/unpacking-the-temi-phone-app-1024x62.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/unpacking-the-temi-phone-app-768x47.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/unpacking-the-temi-phone-app-205x12.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 31: Unpacking the temi phone app<\/p>\n<p>Next, we searched the unpacked APK for the file or code we wanted to modify. For our proof-of-concept, we decided to simply change the label for the \u201cCall\u201d button, since it would be immediately obvious whether or not it worked. In many Android apps, the strings used for buttons are typically not hardcoded and are instead loaded from a resource file. In this case, they were loaded from the file res\/values\/strings.xml:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103632\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/searching-for-the-call-button-label.png\" alt=\"\" width=\"975\" height=\"143\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-for-the-call-button-label.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-for-the-call-button-label-300x44.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-for-the-call-button-label-768x113.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-for-the-call-button-label-205x30.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 32: Searching for the call button label in strings.xml<\/p>\n<p>It looked like line 108 contained the label we wanted to change. We simply replaced \u201cCall\u201d with \u201cPWN\u201d and saved our changes.<\/p>\n<p><em>Note: For less trivial modifications, like the ones we would need to make later, this process would naturally be more involved. Since even the most sophisticated Java decompilers are unable to produce code that will actually compile for non-trivial apps, meaningful modifications usually mean having to read and modify <\/em><a href=\"https:\/\/github.com\/JesusFreke\/smali\"><em>smali<\/em><\/a><em>, the assembler for Android\u2019s Dalvik bytecode. That being said, we found that the best approach to making meaningful changes to a complex app like temi\u2019s is to <strong>read Java, write smali<\/strong>. By this, we mean that it&#8217;s better to do your reversing on decompiled Java code and only look at the nigh-hieroglyphic smali code once you know exactly what changes you want to make and what class to make it in.<\/em><\/p>\n<p>Once our modification had been made, we used Apktool again to repack the APK:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103635\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/repacking-modified-app.png\" alt=\"\" width=\"1430\" height=\"87\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/repacking-modified-app.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/repacking-modified-app-300x18.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/repacking-modified-app-1024x62.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/repacking-modified-app-768x47.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/repacking-modified-app-205x12.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 33: Repacking our modified app<\/p>\n<p>Our next step was to sign the modified APK. This is because Android refuses to install any unsigned APKs, even through ADB. First, we generated a key using Keytool:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103638\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/generating-the-key.png\" alt=\"\" width=\"1430\" height=\"87\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/generating-the-key.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/generating-the-key-300x18.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/generating-the-key-1024x62.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/generating-the-key-768x47.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/generating-the-key-205x12.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 34: Generating the key we will use to sign the modified app<\/p>\n<p>and then we used our key to sign the APK using Jarsigner:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103641\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/signing-the-modified-app.png\" alt=\"\" width=\"1430\" height=\"87\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/signing-the-modified-app.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/signing-the-modified-app-300x18.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/signing-the-modified-app-1024x62.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/signing-the-modified-app-768x47.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/signing-the-modified-app-205x12.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 35: Signing the modified app<\/p>\n<p>Finally, we installed the modified APK onto an Android phone using ADB:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103644\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/installing-modified-app.png\" alt=\"\" width=\"1430\" height=\"168\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/installing-modified-app.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/installing-modified-app-300x35.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/installing-modified-app-1024x120.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/installing-modified-app-768x90.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/installing-modified-app-205x24.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 36: Installing the modified app<\/p>\n<p>After launching the app and selecting one of our contacts, we were greeted with a pleasant sight:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103647\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/testing-modified-app.jpg\" alt=\"\" width=\"1080\" height=\"921\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/testing-modified-app.jpg 1080w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/testing-modified-app-300x256.jpg 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/testing-modified-app-1024x873.jpg 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/testing-modified-app-768x655.jpg 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/testing-modified-app-151x129.jpg 151w\" sizes=\"auto, (max-width: 1080px) 100vw, 1080px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 37: Testing the modified app<\/p>\n<p>Furthermore, the change we made did not seem to impact the app\u2019s functionality; we could still make and receive calls, add contacts, etc.<\/p>\n<p>In our view, this was a vulnerability, later denoted by CVE-2020-16168 and having a CVSS score of 6.5. An altered and potentially malicious app could still access the various cloud resources and user information made available to the temi app because no integrity checks were performed by either the app or the remote servers to ensure that the app had not been modified. As we\u2019ll demonstrate later, the presence of this vulnerability made various other attack vectors possible.<\/p>\n<h3><a name=\"Toc45545109\"><\/a>The Relationship Between Robot IDs and MQTT Topics<\/h3>\n<p>In the overview section, we made the claim that &#8220;each robot has its own MQTT topics that it listens on, which are identified using temi&#8217;s MQTT client ID, otherwise known as its robot ID.\u201d Here we will outline how we came to this conclusion and the specific format of a few of the topics that temi and its phone app subscribe\/publish to.<\/p>\n<p>Since we knew that temi uses MQTT to handle calling notifications, a natural place to start our investigation was the code used by the phone app to initiate calls. Moving down the call chain, we saw that the robot ID was being passed to the sendInitInvitation() method of the TelepresenceService class:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103650\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/InvitationManager.png\" alt=\"\" width=\"975\" height=\"380\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/InvitationManager.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/InvitationManager-300x117.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/InvitationManager-768x299.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/InvitationManager-205x80.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 38: InvitationManagerImpl.sendInitInvitation()<\/p>\n<p>Here, the robot ID is used in two different places. On line 82, an MqttDelegateApi.Topic object is created with the name &#8220;users\/&lt;ROBOT_ID&gt;\/status&#8221;, <em>implying that each robot has its own MQTT topic category for its status and the robot ID itself is used to uniquely identify these topics<\/em>. Next, on line 87, we see one of many <a href=\"https:\/\/github.com\/ReactiveX\/RxJava\">RxJava<\/a> functions (the others have been omitted) with the robot ID passed as a parameter to it. This function\u2019s only purpose is to call InvitationManagerImpl.sendInviteMsg(), passing along the Invitation and the robot ID as its arguments:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103653\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/invitation-and-robot-ID.png\" alt=\"\" width=\"975\" height=\"743\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/invitation-and-robot-ID.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/invitation-and-robot-ID-300x229.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/invitation-and-robot-ID-768x585.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/invitation-and-robot-ID-169x129.png 169w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 39: InvitationManagerImpl.sendInviteMsg()<\/p>\n<p>This function is of particular interest because we see the construction of another MQTT topic name on line 332, this time taking the form &#8220;client\/&lt;ROBOT_ID&gt;\/invite&#8221;. Presumably, this is the topic format used when publishing call invitations to specific temi robots (and, likely, phone contacts).<\/p>\n<p>Additionally, the anonymous function executed via doOnSuccess() uses MqttManager.publish() (line 359) to actually publish the call invitation on the callee&#8217;s call invite topic. This information would become useful when we later tried to send custom MQTT messages to our temi in order to facilitate privilege escalation.<\/p>\n<h3><a name=\"Toc45545110\"><\/a>How are MQTT Call Invite Messages Published?<\/h3>\n<p>If we were to publish our own MQTT messages, we would need a robust understanding of the code used by the temi phone app to publish arbitrary MQTT messages, which appears to be this MqttManagerImpl.publish() method.<\/p>\n<p>To determine what was actually being passed to publish(), we needed to go through what each of the RxJava functions in InvitationManagerImpl.sendInviteMsg() was doing. Referring back to <u>Figure 39<\/u>:<\/p>\n<ul>\n<li>On lines 331-339, <a href=\"http:\/\/reactivex.io\/documentation\/operators\/zip.html\">Single.zip()<\/a> is called, which simply creates a Pair from the MQTT topic string (&#8220;client\/&lt;ROBOT_ID&gt;\/invite&#8221;) and the Invitation object.<\/li>\n<li>On lines 340-355, <a href=\"http:\/\/reactivex.io\/documentation\/operators\/flatmap.html\">Single.flatMap()<\/a> is called, which adds a timestamp to the Invitation object inside the Pair.<\/li>\n<li>Presuming successful execution of the previous flatMap() call, <a href=\"https:\/\/www.oreilly.com\/library\/view\/learning-rxjava\/9781787120426\/ac721fa0-e59e-4a16-9173-17175a1b83ea.xhtml\">Single.doOnSuccess()<\/a> is called on lines 356-372. As mentioned previously, this is where the call to MqttManagerImpl.publish() occurs. Since this doOnSuccess() operates on the value returned by the previous call to flatMap(), the arguments being passed to publish() are:<\/li>\n<\/ul>\n<table>\n<tbody>\n<tr>\n<td width=\"361\"><strong>Argument<\/strong><\/td>\n<td width=\"210\"><strong>Description<\/strong><\/td>\n<\/tr>\n<tr>\n<td width=\"361\">(String) pair.first<\/td>\n<td width=\"210\">The MQTT topic string<\/td>\n<\/tr>\n<tr>\n<td width=\"361\">new Gson().toJson((Invitation) pair.second)<\/td>\n<td width=\"210\">The Invitation object as JSON<\/td>\n<\/tr>\n<tr>\n<td width=\"361\">0<\/td>\n<td width=\"210\">The integer \u201c0\u201d<\/td>\n<\/tr>\n<tr>\n<td width=\"361\">false<\/td>\n<td width=\"210\">The boolean \u201cfalse\u201d<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<p>While it was obvious that the first argument is the MQTT topic the message is being published on and the second argument is the message itself, it was not immediately obvious what the third and fourth arguments were for. Digging down into the source code of the MQTT package being used (<a href=\"https:\/\/www.eclipse.org\/paho\/files\/javadoc\/org\/eclipse\/paho\/client\/mqttv3\/package-summary.html\">org.eclipse.paho.client.mqttv3<\/a>) revealed their purpose:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103656\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/purpose.png\" alt=\"\" width=\"975\" height=\"142\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/purpose.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/purpose-300x44.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/purpose-768x112.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/purpose-205x30.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 40: MqttAsyncClient.publish()<\/p>\n<p>After passing through a couple MqttManagerImpl methods, the four arguments listed above become the first four arguments passed to this internal publish() method. The JSON string (second argument), is converted from a string to a byte array in the interim; the rest of the arguments are unchanged.<\/p>\n<p>Knowing this, it was clear that the second argument is indeed the MQTT message, the third argument is the <a href=\"https:\/\/www.hivemq.com\/blog\/mqtt-essentials-part-6-mqtt-quality-of-service-levels\/\">QoS<\/a>, and the fourth argument is a flag that specifies whether or not the message should be <a href=\"https:\/\/www.hivemq.com\/blog\/mqtt-essentials-part-8-retained-messages\/\">retained<\/a>. A QoS value of \u201c0\u201d means that the message will be delivered to clients subscribed to the topic at most once. A retained flag of \u201cfalse\u201d means that new subscribers to the topic will not receive the most recent published message upon subscription.<\/p>\n<h3><a name=\"Toc45545111\"><\/a>Intercepting Calls<\/h3>\n<p>As we&#8217;ve already established, every temi robot has a unique MQTT client ID and many of the topics it subscribes to contain this ID to indicate that they are intended for that specific robot. If users of the temi phone app can receive calls in addition to making them, it stands to reason that they must also have a unique MQTT client ID \u2013 a robot ID equivalent. If there was an easy way to discover the client ID of another phone app user, it might be possible to subscribe to their topics and thus receive calls intended for them, making it worthy of investigation.<\/p>\n<p>If we refer back to <u>Figure 22<\/u>, we saw that if a call is initiated from the Recent Calls screen, a method called getMd5PhoneNumber() is used to obtain the client ID of the callee. While a temi doesn\u2019t have a phone number to speak of, we began to suspect that the client ID for users of the temi phone app might just be an MD5 hash of their phone number.<\/p>\n<p>Although we could have followed the code in order to track down exactly where this value comes from, we thought it might be easier to simply verify our suspicions. To do this, we first took the MD5 hash of the temi admin\u2019s phone number and then performed a string search for this hash in every temi-related file we had access to.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103659\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/Google-Voice-number.png\" alt=\"\" width=\"1080\" height=\"619\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Google-Voice-number.png 1080w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Google-Voice-number-300x172.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Google-Voice-number-1024x587.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Google-Voice-number-768x440.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/Google-Voice-number-205x117.png 205w\" sizes=\"auto, (max-width: 1080px) 100vw, 1080px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 41: The Google Voice number used to register with temi<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103662\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number.png\" alt=\"\" width=\"1876\" height=\"388\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number.png 1876w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number-300x62.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number-1024x212.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number-768x159.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number-1536x318.png 1536w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/MD5-hash-of-the-phone-number-205x42.png 205w\" sizes=\"auto, (max-width: 1876px) 100vw, 1876px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 42: Taking the MD5 hash of the phone number<\/p>\n<p>Sure enough, this hash appeared in two places. First, it appeared in the primary SQLite 3 database file for the temi app running on the robot itself. More specifically, it appears in the \u201cRecentCallModel\u201d table under the \u201cuserId\u201d column for the table&#8217;s only entry. Based on the table&#8217;s name and sole entry, this is almost certainly used to store temi&#8217;s recent calls, as its admin was the only user it had called at this point.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103665\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/matching-string.png\" alt=\"\" width=\"1320\" height=\"288\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-string.png 1320w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-string-300x65.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-string-1024x223.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-string-768x168.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-string-205x45.png 205w\" sizes=\"auto, (max-width: 1320px) 100vw, 1320px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 43: The matching string in temi&#8217;s RecentCallModel table, part of its SQLite 3 Database<\/p>\n<p>The second match was in the log output we saved after running logcat on our temi earlier, the same log output seen in <u>Figure 30<\/u>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103668\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/matching-strings.png\" alt=\"\" width=\"975\" height=\"240\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-strings.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-strings-300x74.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-strings-768x189.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/matching-strings-205x50.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 44: Matching strings in the logcat output recorded earlier<\/p>\n<p>This log output appears to conclusively confirm our suspicions. With what we knew now, these log messages appeared to be JSON data being sent to\/received from the MQTT broker. Moreover, the &#8220;topic&#8221; string in the first result exactly matched the MQTT topic format string found on line 82 of <u>Figure 38<\/u>. The only difference is that in that case, the string between \u201cusers\/\u201d and \u201c\/status\u201d was the robot ID; here, it was an MD5 hash of the user\u2019s phone number.<\/p>\n<p>Since we now knew that<\/p>\n<ol>\n<li>temi robots and phone app users received calls on the topic &#8220;client\/&lt;CLIENT_ID&gt;\/invite&#8221;, where &#8220;&lt;CLIENT_ID&gt;&#8221; is the MQTT client ID of the callee,<\/li>\n<li>the MQTT client ID for phone app users was simply an MD5 hash of the phone number used to register with the app, and<\/li>\n<li>we could alter the existing phone app,<\/li>\n<\/ol>\n<p>it stood to reason that we could modify the app to subscribe to another user\u2019s call invite topic in order to intercept calls intended for that user as long as we knew the user\u2019s phone number. In theory, this would allow an attacker to effectively impersonate another user and spy on them by intercepting their calls. The question then became: What code needs to be modified in order to get this to happen? Well, we&#8217;re looking at a situation where temi initiates a call with its admin and an attacker attempts to intercept that call. In the case of calls initiated by the phone app, we discovered that call invitations are sent via InvitationManagerImpl.sendInviteMsg(), which publishes the call invite message on the topic &#8220;client\/&lt;ROBOT ID&gt;\/invite&#8221;. We suspected a similar approach was being used when a call is initiated from a temi to a phone user and decided to investigate to confirm.<\/p>\n<p>Luckily for us, the exact same InvitationManagerImpl.sendInviteMsg() method could be found in the temi robot\u2019s code, and it even seemed to function identically. Thus, it was probably safe to assume that the robot initiates calls with phone users in the same way: by publishing a call invitation to the topic &#8220;client\/&lt;CLIENT ID&gt;\/invite&#8221;, CLIENT_ID being the MQTT client ID of the callee.<\/p>\n<p>If the caller publishes their call invites to a certain MQTT topic, it follows that the callee must subscribe to that same topic to receive the invite. Now that we knew the format of the call invite topic, the next step was to track down the code used by the Android app to subscribe to this topic so we could alter it to subscribe to the temi admin&#8217;s topic instead.<\/p>\n<p>Performing a string search for this pattern in the decompiled phone app&#8217;s code produced three unique results:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103674\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/45.png\" alt=\"\" width=\"1430\" height=\"189\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/45.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/45-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/45-1024x135.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/45-768x102.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/45-205x27.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 45: Searching for &#8220;client\/.+\/invite&#8221;<\/p>\n<p>The second result we recognized as being part of the code used to generate an outgoing call invitation. Thus, we were only interested in the first and third results.<\/p>\n<p>We began by looking at the first result, found on line 170 of InvitationManagerImpl.java:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103677\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/46.png\" alt=\"\" width=\"624\" height=\"190\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/46.png 624w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/46-300x91.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/46-205x62.png 205w\" sizes=\"auto, (max-width: 624px) 100vw, 624px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 46: InvitationManagerImpl.sendInviteAbortMsg()<\/p>\n<p>This reference is part of the method InvitationManagerImpl.sendInvitationAbortMsg(). Since we were interested in the code that subscribes to the call invite topic and not the code that publishes messages to it, we moved on.<\/p>\n<p>The third result was found on line 523 of MqttManagerImpl.java:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103680\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/47.png\" alt=\"\" width=\"1430\" height=\"128\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/47.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/47-300x27.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/47-1024x92.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/47-768x69.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/47-205x18.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 47: MqttManagerImpl.buildInviteTopic()<\/p>\n<p>This didn\u2019t tell us anything about how the generated topic is used, so we took a step back and looked at what code invokes this method:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103683\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/48.png\" alt=\"\" width=\"975\" height=\"674\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/48.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/48-300x207.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/48-768x531.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/48-187x129.png 187w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 48: MqttManagerImpl.lambda$initMqttClient$13()<\/p>\n<p>The call to buildInviteTopic() can be seen on line 408. There&#8217;s a lot going on in this method, but at a high level it appears that it is responsible for setting the callback functions for the MqttManager, which are being defined inline. More specifically, the invite topic string generated by buildInviteTopic() is used in the connectComplete() callback function, where it is passed as the first parameter to MqttManagerImpl.subscribe().<\/p>\n<p>As expected, MqttManager&#8217;s subscribe() method is used to a subscribe to a particular MQTT topic, with the first parameter being the topic string:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-103686 aligncenter\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/49.png\" alt=\"\" width=\"975\" height=\"408\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/49.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/49-300x126.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/49-768x321.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/49-205x86.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 49: MqttMangerImpl.subscribe()<\/p>\n<p>Thus, it appeared that we had found the code being used to subscribe to the call invite MQTT topic. Based on these findings, we decided the simplest approach would be to change the call to MqttManagerImpl.subscribe() on line 408 of <u>Figure 48<\/u> \u2013 instead of passing it the topic string returned by MqttManagerImpl.buildInviteTopic(), we would instead hard-code it to pass the temi admin&#8217;s call invite MQTT topic string.<\/p>\n<p>Using this approach, we were able to construct a modified temi phone app that would receive all calls intended for another user, as shown in the following video:<\/p>\n<p align=\"center\"><iframe loading=\"lazy\" src=\"https:\/\/www.youtube.com\/embed\/dyHcxiZ_z9E\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>Here, the vulnerability was the lack of any authentication when publishing or subscribing to arbitrary topics, denoted by CVE-2020-16167 and having a CVSS score of 8.6. At a minimum, a check should have been made to ensure that clients cannot subscribe to another client\u2019s topics.<\/p>\n<h3><a name=\"Toc45545112\"><\/a>Problem: temi Won\u2019t Answer My Calls<\/h3>\n<p>Our next goal was to gain the ability initiate a call with a temi and have it automatically answer. As mentioned previously, this capability is typically reserved for the temi\u2019s admin and users explicitly authorized by the admin. Thus, if an attacker wishes to spy on a temi user, they would need to trick temi into thinking the call is originating from one of these authorized users.<\/p>\n<p>With no other leads, we began searching through the robot\u2019s codebase for keywords related to user permissions, ownership, admin, etc. until we found a very promising enum class:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103689\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/class-used.png\" alt=\"\" width=\"975\" height=\"254\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/class-used.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/class-used-300x78.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/class-used-768x200.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/class-used-205x53.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 50: Class used for delineating the various roles held by temi&#8217;s users<\/p>\n<p>This enum is used in com.roboteam.teamy.users.User, the class used to store information about individual temi users, with User.role being an object of the Role enum class:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103692\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/51.png\" alt=\"\" width=\"975\" height=\"338\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/51.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/51-300x104.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/51-768x266.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/51-205x71.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 51: Class used to describe individual temi users\/contacts<\/p>\n<p>Going back to <u>Figure 50<\/u>, if we assume that Role.ADMIN refers to the user that originally registered temi and that Role.CONTACT refers to a normal user, then Role.OWNER likely refers to a user that has been authorized to &#8220;hop in&#8221; to the robot by the admin.\u00a0To verify this, we looked for places in the code where this enum is used. There are 59 unique references to the Role class, but we&#8217;ll only be looking at the pertinent ones here.<\/p>\n<p>The first reference we\u2019ll be looking at appears on lines 351 and 357 of ConferencePresenter.handleViewForCallTypes():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103695\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/52.png\" alt=\"\" width=\"975\" height=\"548\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/52.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/52-300x169.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/52-768x432.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/52-205x115.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 52: ConferencePresenter.handleViewForCallTypes()<\/p>\n<p>On line 351, a string comparison is performed between this.resourcesLoader.getString(R.string.privacy_support_support_caller_name) and this.callerDisplayName; if the two match, a new User is created with a role of CONTACT. So just what is this string that the \u2018if\u2019 statement is checking against? We decided to take a look at where this constant is defined:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103698\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/53.png\" alt=\"\" width=\"975\" height=\"73\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/53.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/53-300x22.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/53-768x58.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/53-205x15.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 53: Searching for &#8220;privacy_support_support_caller_name&#8221;<\/p>\n<p>Taken together, this means that if the caller&#8217;s display name is exactly &#8220;Support&#8221;, a new User is created with CONTACT privileges. This check likely exists in the event that a member of temi&#8217;s support staff calls a user. While this was certainly interesting, it is a niche scenario.<\/p>\n<p>What happens if the caller&#8217;s name is not &#8220;Support&#8221;? In that case, the else statement on line 352 is hit, which simply sets user to the result of getUserbyPeerId():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103701\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/54.png\" alt=\"\" width=\"975\" height=\"268\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/54.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/54-300x82.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/54-768x211.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/54-205x56.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 54: ConferencePresenter.getUserByPeerId()<\/p>\n<p>This method tries to obtain the User associated with the current caller by performing a lookup in temi&#8217;s UsersRepository using the caller&#8217;s MQTT client ID. If the lookup succeeds, the found User is returned; if it fails, a new User is created with Role.CONTACT privileges.<\/p>\n<p>As mentioned previously, <u>Figure 52<\/u> contains two references to the Role class. Let&#8217;s now look at the second reference, found on line 357. Here, the role of the user is checked. If they are either an ADMIN or an OWNER, temi:<\/p>\n<ul>\n<li>waits 1.5 seconds (line 362)<\/li>\n<li>checks if the call hasn&#8217;t already ended (line 365)<\/li>\n<li>if it hasn&#8217;t, answers the incoming call (line 370)<\/li>\n<\/ul>\n<p>Otherwise, the function returns after setting the app&#8217;s view for an incoming call.<\/p>\n<h2><a name=\"Toc45545113\"><\/a>Solution: Become an OWNER<\/h2>\n<p>To recap what we&#8217;ve learned thus far:<\/p>\n<ul>\n<li>The role of ADMIN appears to be reserved for the user that originally registers temi via QR code scan.<\/li>\n<li>If the user calling temi <em><u>is not<\/u><\/em> recognized, a new User object is created for them with CONTACT privileges.<\/li>\n<li>If the user calling temi <em><u>is<\/u><\/em> recognized, their role is checked:\n<ul>\n<li>If they are a CONTACT, temi waits for the call to be answered via touchscreen.<\/li>\n<li>If they are either an ADMIN or an OWNER, temi answers the call automatically<strong>. This is the behavior we want.<\/strong><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Scanning the temi&#8217;s QR code for the purposes of registration is only possible when temi is first powered on or after performing a factory reset. Thus, the attacker cannot realistically make themselves an ADMIN. And since CONTACT privileges are insufficient to get temi to pick up automatically, our best bet was to figure out how to obtain the role of OWNER.<\/p>\n<h3><a name=\"Toc45545114\"><\/a>Adding an OWNER: Phone App\u2019s Perspective<\/h3>\n<p>Although we knew that it was possible to promote a user to an OWNER using the phone app and we <em>suspected<\/em> that was achieved via MQTT, we still weren\u2019t sure of what goes on \u201cunder the hood\u201d to make that happen.<\/p>\n<p>After some searching, we found that AddOwnersPresenter.addOwners() is called whenever an admin selects one or more users to be granted OWNER privileges:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103704\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/55.png\" alt=\"\" width=\"975\" height=\"254\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/55.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/55-300x78.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/55-768x200.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/55-205x53.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 55: AddOwnersPresenter.addOwners(), trimmed<\/p>\n<p>Here, selectedIds refers to the MQTT client IDs of the users selected to be promoted and robotId refers to the MQTT client ID of the temi robot this method is granting permissions for.<\/p>\n<p>The majority of this method\u2019s body has been trimmed because we\u2019re really only concerned with what\u2019s happening on lines 104-106, which seems to handle sending the request to add an OWNER \u2013 the rest of the method is dedicated to logging and updating local databases to reflect the new OWNERs.<\/p>\n<p>This request is sent by first fetching the unique private key used to identify this app\u2019s instance (line 101), and then creating a new AddRemoveOwnersRequest using the selectedIds, robotId, and this private key:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103707\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/56.png\" alt=\"\" width=\"1430\" height=\"1316\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/56.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/56-300x276.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/56-1024x942.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/56-768x707.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/56-140x129.png 140w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 56: AddRemoveOwnersRequest<\/p>\n<p>The constructor for AddRemoveOwnersRequest creates a timestamp for the request, then creates a new AddOwnersRequestRequest, which contains the body of the request, and finally uses the passed in privateKey in order generate a signature for the AddOwnersRequestRequest. In other words, AddRemoveOwnersRequest is nothing more than a wrapper for the real request and is used to store its signature.<\/p>\n<p>We decided to look at this AddOwnersRequestRequest object next. While the ownerIds, robotId, and timestamp members were mostly self-explanatory, source and type were less so. Looking back at line 8, we saw that source was being set to a hardcoded value of \u201cADMIN\u201d, which seemed to imply that this was the origin of the request. Looking back at line 6, we saw that type is simply the passed in OwnersRequestType enum (in our case, OWNERS_ADD_TYPE) converted to a string. This enum, defined near the bottom of the class, can take on two values: OWNERS_ADD_TYPE and OWNERS_REMOVE_TYPE. This implied that this same structure was recycled for requests meant to demote OWNERs back to CONTACTs.<\/p>\n<p>Thus, we determined that AddRemoveOwnersRequests had the following structure:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103710\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/57.png\" alt=\"\" width=\"721\" height=\"114\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/57.png 721w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/57-300x47.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/57-205x32.png 205w\" sizes=\"auto, (max-width: 721px) 100vw, 721px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 57: Anatomy of an AddRemoveOwnersRequest<\/p>\n<p>Now that we knew the structure of these requests, we next wanted to know how they were being sent and where. To this end, we decided to look at OwnersApi.addOwners(), which, according to <u>Figure 55<\/u>, is actually sending the AddRemoveOwnersRequest.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103713\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/58.png\" alt=\"\" width=\"975\" height=\"353\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/58.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/58-300x109.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/58-768x278.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/58-205x74.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 58: OwnersApi.addOwners()<\/p>\n<p>The body of this method didn\u2019t tell us much, but the imports for the OwnersApi class gave us a clue: this was using the <a href=\"https:\/\/square.github.io\/retrofit\/\">Retrofit 2 HTTP Client for Android<\/a>, which is typically used to send HTTP requests to a REST API. According to <a href=\"https:\/\/code.tutsplus.com\/tutorials\/sending-data-with-retrofit-2-http-client-for-android--cms-27845\">this Retrofit tutorial<\/a>, the @POST annotation \u201cindicates that we want to execute a POST request when this method is called\u201d and \u201cthe argument value for the @POST annotation is the endpoint.\u201d The @Body annotation, on the other hand, indicates that we want the AddRemoveOwnersRequest to serve as the body of the POST request.<\/p>\n<p>Okay, so this method is simply sending the request to add an OWNER via POST to some REST server at the endpoint \u201cownership\/admin\/add\/owners\u201d. Our next question became: Where is this REST server located?<\/p>\n<p>Part 5 of that same Retrofit tutorial told us that the getClient() method is typically used to obtain\/create a Retrofit instance, and the only argument it takes is a string containing the URL for the REST server. Searching for \u201cgetClient()\u201d in the phone app\u2019s code led us to ApiClient.getClient():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103716\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/59.png\" alt=\"\" width=\"1430\" height=\"128\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/59.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/59-300x27.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/59-1024x92.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/59-768x69.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/59-205x18.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 59: ApiClient.getClient()<\/p>\n<p>Working backwards from this method, we were able to track down the server\u2019s URL:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103719\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/URLs-for-MQTT-broker-and-REST-server.png\" alt=\"\" width=\"624\" height=\"56\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/URLs-for-MQTT-broker-and-REST-server.png 624w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/URLs-for-MQTT-broker-and-REST-server-300x27.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/URLs-for-MQTT-broker-and-REST-server-205x18.png 205w\" sizes=\"auto, (max-width: 624px) 100vw, 624px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 60: URLs for the MQTT broker and REST server<\/p>\n<p>This URL confirmed that the recipient of this request was <em>not<\/em> the temi robot and it was <em>not<\/em> being sent via MQTT, contrary to our initial assumptions. This begged the question: If the phone app wasn\u2019t sending these requests to temi, how was temi being notified of any updates to the privileges of its users? We hypothesized that this REST server was simply a middleman whose job was to authenticate all requests to add\/remove OWNERs by checking the request\u2019s signature against the admin\u2019s public key that was saved during temi\u2019s initial registration process. This extra level of authentication made sense since privilege management was a particularly sensitive functionality. Presumably, this same REST server would then forward the request to the robot if it determined that the request\u2019s signature was valid.<\/p>\n<p>We took some time trying to send these requests from a user that wasn\u2019t temi\u2019s admin, but they failed, lending some credence to our theory. This was looking like a dead end.<\/p>\n<h3><a name=\"Toc45545115\"><\/a>Adding an OWNER: temi\u2019s Perspective<\/h3>\n<p>Well, if we couldn\u2019t successfully spoof the request the phone app sends to the REST server, perhaps we could instead spoof the request the server sends to temi, bypassing the authentication mechanism altogether. We started looking for the code temi used to handle these requests.<\/p>\n<p>Searching specifically for &#8220;Role.OWNER&#8221; in the temi&#8217;s decompiled code led us to OwnersController$getUsersRepository$2.apply():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103722\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/61.png\" alt=\"\" width=\"975\" height=\"227\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/61.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/61-300x70.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/61-768x179.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/61-205x48.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 61: OwnersController$getUsersRepository$2.apply()<\/p>\n<p>Starting with OwnersController$getUsersRepository$2, we moved up the call chain in an attempt to discover how temi processes requests to add OWNERs. More concretely, this was accomplished through a liberal use of the &#8220;Find Usage&#8221; feature in JADX, which can be done by simply right-clicking virtually any symbol. Although convenient, &#8220;Find Usage&#8221; would often fail when the method in question was not invoked directly, such as when an implementation of an abstract class\/interface served as the middleman. In such cases, we would perform a string search for instances where the method was invoked in the smali code. To help separate invocations from declarations, we took advantage of the smali syntax for method calls, which consisted of the name of the class the method belonged to, followed by a right arrow (-&gt;), followed by the method&#8217;s signature.<\/p>\n<p>As an example, &#8220;Find Usage&#8221; failed for the accept() method of the class UsersAdminTopicListener$listenToUserAdminTopic$1, so to find where it&#8217;s invoked, we ran:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103725\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/62.png\" alt=\"\" width=\"1430\" height=\"128\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/62.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/62-300x27.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/62-1024x92.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/62-768x69.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/62-205x18.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 62: Searching for UsersAdminTopicListener$listenToUserAdminTopic$1.accept() in the smali code<\/p>\n<p>For especially indirect invocations, like dependency injection, more creative means had to be used, but a combination of &#8220;Find Usage&#8221; and string searches such as these got us 99% of the way there.<\/p>\n<p>Using this approach, we found that the mechanism begins with the UsersAdminTopicListener class, which, as the name suggests, handles listening on temi&#8217;s \u201cusers admin\u201d topic. Moving down the call chain from here, we found out how messages received on this topic are processed by temi and ultimately used to alter the Role, or privilege level, of certain contacts. Based on our earlier analysis, it was likely that the REST server would forward the request sent by the phone app to this MQTT topic.<\/p>\n<p>We found that the bulk of the work is performed by a method called listenToUserAdminTopic():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103728\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/63.png\" alt=\"\" width=\"1430\" height=\"373\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/63.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/63-300x78.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/63-1024x267.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/63-768x200.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/63-205x53.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 63: UsersAdminTopicListener.listenToUserAdminTopic()<\/p>\n<p>This function does several things. First, on line 50, it creates a <a href=\"http:\/\/reactivex.io\/RxJava\/2.x\/javadoc\/io\/reactivex\/Flowable.html\">Flowable<\/a> object by calling UsersAdminTopicListener.getOwnerRequestFlowable(). Next, on lines 51-56, it subscribes to this Flowable and for each emitted OwnersRequest, it calls OwnersController.handle$app_usaDemoRelease() upon success or simply logs upon failure.<\/p>\n<p>We decided to first look at the code for getOwnerRequestFlowable():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103731\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/64.png\" alt=\"\" width=\"1430\" height=\"722\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/64.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/64-300x151.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/64-1024x517.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/64-768x388.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/64-205x104.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 64: UsersAdminTopicListener.getOwnerRequestFlowable()<\/p>\n<ul>\n<li>It begins by first converting an Observable obtained via mqttPipeline.observe() into a Flowable.<\/li>\n<li>Next, it throws out all emitted MqttMessages whose topic doesn&#8217;t match the regex &#8220;users\/.+\/admin&#8221; via <a href=\"http:\/\/reactivex.io\/documentation\/operators\/filter.html\">RxJava&#8217;s filter() method<\/a>.<\/li>\n<li><a href=\"http:\/\/reactivex.io\/documentation\/operators\/map.html\">RxJava&#8217;s map() method<\/a> is then used convert the body of the MqttMessage from JSON to an object of the OwnersMessage<\/li>\n<li>Finally, map() is used a second time to extract and return the OwnersRequest from each OwnersMessage.<\/li>\n<\/ul>\n<p>At this point, we decided it would be useful to understand the structure of OwnersRequests and OwnersMessages, since these seem to be key to temi\u2019s privilege management mechanisms:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103734\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/anatomy-of-an-OwnersMessage.png\" alt=\"\" width=\"601\" height=\"94\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/anatomy-of-an-OwnersMessage.png 601w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/anatomy-of-an-OwnersMessage-300x47.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/anatomy-of-an-OwnersMessage-205x32.png 205w\" sizes=\"auto, (max-width: 601px) 100vw, 601px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 65: Anatomy of an OwnersMessage<\/p>\n<p>Put briefly, each OwnersMessage is nothing more than a wrapper for an OwnersRequest, which consists of ownerIds, a list of the MQTT client IDs of the users whose privileges are being modified, and type, which indicates whether the request is trying to promote a CONTACT to an OWNER (OWNERS_ADD) or demote an OWNER back to a CONTACT (OWNERS_REMOVE).<\/p>\n<p>Comparing this to <u>Figure 57<\/u>, an OwnersMessage appears to be an AddRemoveOwnersRequest without the signature. Similarly, an OwnersRequest appears to be a stripped-down AddOwnersRequestRequest, with the robotId, source, and timestamp omitted. This meshes well with our earlier hypothesis that the REST server\u2019s job is to authenticate and forward AddRemoveOwnersRequests to the temi robot. The signature, timestamp, and source would be omitted since they\u2019ve already been verified by the server; the robotId, while needed by the server to know where to forward the request, becomes redundant once the request reaches temi.<\/p>\n<p>Our next step was to figure out what handle$app_usaDemoRelease() does. Since it\u2019s quite the rabbit hole, however, we will summarize its effects in lieu of venturing down it:<\/p>\n<ol>\n<li>It queries the Users table in temi\u2019s local database for all users with IDs matching any of the ones in the OwnersRequest\u2019s ownerIds<\/li>\n<li>It replaces the Role for each of these users with one corresponding to the OwnersRequest\u2019s type: OWNERS_ADD\u2192 OWNER, OWNERS_REMOVE\u00a0\u2192 CONTACT.<\/li>\n<li>It updates temi\u2019s Users table with the updated user information.<\/li>\n<\/ol>\n<p>This was promising, since it meant that we could potentially trick temi into promoting an arbitrary user\/contact to an OWNER simply by crafting a custom OwnersRequest and publishing it on temi&#8217;s &#8220;users\/&lt;ROBOT_ID&gt;\/admin&#8221; topic, thereby bypassing the authentication server entirely.<\/p>\n<p>Unfortunately, part 1 above reveals a potential obstacle for this plan: since temi will only process OwnersRequests for users already present in its local Users table, we must first add ourselves to this table for this strategy to succeed. Recalling our earlier analysis of how temi handles unrecognized callers, one way of accomplishing this was to simply cold call temi, which would cause it to automatically add the caller to its contacts list. This was far from ideal, however, since cold calling\u00a0 temi without already having the role of OWNER or ADMIN would cause it to ring and display the caller&#8217;s username on its screen, potentially alerting temi\u2019s users that something weird is going on.<\/p>\n<h3><a name=\"Toc45545116\"><\/a>Detour: Sneaking Onto temi\u2019s Contact List<\/h3>\n<p>Before continuing on, we decided to take a brief detour to find a better way for an attacker to add themselves to temi\u2019s contact list.<\/p>\n<p>From our prior investigation into how temi implements its various privilege levels through the use of Roles, we discovered that temi uses the User class to define its various users. Thus, it follows that any code used to add a new user to temi&#8217;s local contact list would first create a new User object, so that&#8217;s exactly what we searched for.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103737\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/searching-temi-code.png\" alt=\"\" width=\"1430\" height=\"189\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-temi-code.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-temi-code-300x40.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-temi-code-1024x135.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-temi-code-768x102.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/searching-temi-code-205x27.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 66: Searching temi&#8217;s code for &#8220;new User(&#8220;, trimmed<\/p>\n<p><u>Figure 66<\/u> shows the result that we followed up on, the others have been omitted. The name of the containing class, SyncContactsController, sounded promising on its own since syncing contacts from temi\u2019s ADMIN would likely involve adding new contacts without needing to start a call, which was exactly what we were trying to do.<\/p>\n<p>Using largely the same strategy we employed for tracing the code flow for adding OWNERs (JADX&#8217;s &#8220;Find Usage&#8221; feature + grepping the smali code), we were able to trace the code flow all the way back to the app&#8217;s entry point. With a more holistic understanding of the mechanism used to sync contacts, we realized that the process is ultimately kicked off by SyncContactsTopicListener.subscribeMqttPipeline():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103740\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/67.png\" alt=\"\" width=\"975\" height=\"435\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/67.png 975w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/67-300x134.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/67-768x343.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/67-205x91.png 205w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 67: SyncContactsTopicListener.subscribeMqttPipeline()<\/p>\n<p>The first thing this method does is take this.mqttPipeline and turns it first into an <a href=\"http:\/\/reactivex.io\/documentation\/observable.html\">Observable <\/a>(using MqttPipeline.observe()) and then into a <a href=\"http:\/\/reactivex.io\/RxJava\/2.x\/javadoc\/io\/reactivex\/Flowable.html\">Flowable<\/a> (using RxJavaInterop.toV2Flowable()), as seen on lines 29 and 30.<\/p>\n<p>Essentially, this.mqttPipeline acts as a relay. Incoming MQTT messages are pushed to the relay using its push() method, which it then relays to all its observers.<\/p>\n<p>The output of this relay is then filtered on lines 31-38 to only return MQTT messages received on topics matching the regex &#8220;synccontacts\/.+&#8221;. Based on the other MQTT topic strings we&#8217;ve seen up to this point \u2013<\/p>\n<ul>\n<li>&#8220;users\/&lt;CLIENT_ID&gt;\/status&#8221;<\/li>\n<li>&#8220;users\/&lt;CLIENT_ID&gt;\/admin&#8221;<\/li>\n<li>&#8220;client\/&lt;CLIENT_ID&gt;\/invite&#8221;<\/li>\n<\/ul>\n<p>\u2013 we were fairly certain temi\u2019s client ID goes after the forward slash. Thus, temi appeared to be listening on the MQTT topic \u201csynccontacts\/&lt;CLIENT_ID&gt;\u201d for messages regarding contact synchronization.<\/p>\n<p>On lines 39-43, the now-filtered MQTT messages emitted by the relay are passed to a call to RxJava&#8217;s <a href=\"http:\/\/reactivex.io\/RxJava\/2.x\/javadoc\/io\/reactivex\/Flowable.html#map-io.reactivex.functions.Function-\">map()<\/a> function, which converts each incoming MQTT message from JSON to an object of the SyncContactsMessage class. We quickly determined that SyncContactsMessages had the following structure:<\/p>\n<p style=\"text-align: center;\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103743\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/68.png\" alt=\"\" width=\"401\" height=\"94\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/68.png 401w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/68-300x70.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/68-205x48.png 205w\" sizes=\"auto, (max-width: 401px) 100vw, 401px\" \/><br \/>\nFigure 68: Anatomy of a SyncContactsMessage<\/p>\n<p>Put briefly, each SyncContactsMessage consisted of a senderClientId, a string holding the MQTT client ID of the request\u2019s sender, and contacts, a list of ContactEntry objects. Each ContactEntry object in the list corresponded to a contact to be synced to temi\u2019s contact list, containing both their clientId and their name.<\/p>\n<p>Finally, on lines 45-49, SyncContactsController.save() would be called on each SyncContactsMessage spit out by the prior call to map():<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-104385\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/69.png\" alt=\"\" width=\"1430\" height=\"1131\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/69.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/69-300x237.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/69-1024x810.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/69-768x607.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/69-163x129.png 163w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 69: SyncContactsController.save()<\/p>\n<p>This method is doing a lot, but we\u2019ll focus on only the most pertinent side-effects:<\/p>\n<ul>\n<li>On lines 53-62, <strong>all messages where the <\/strong><strong>senderClientId<\/strong><strong> does not match the temi admin&#8217;s client ID are discarded<\/strong>. This will become important later.<\/li>\n<li>On lines 63-75, the list of contacts is extracted from the SyncContactsMessage and is used to build a list of User objects \u2013 one per contact. The Users produced by this method are initialized in the following manner:<\/li>\n<\/ul>\n<table>\n<tbody>\n<tr>\n<td width=\"120\"><strong>Member<\/strong><\/td>\n<td width=\"300\"><strong>Assigned Value<\/strong><\/td>\n<\/tr>\n<tr>\n<td width=\"120\">User.id<\/td>\n<td width=\"300\">ContactEntry.clientId<\/td>\n<\/tr>\n<tr>\n<td width=\"120\">User.name<\/td>\n<td width=\"300\">ContactEntry.name<\/td>\n<\/tr>\n<tr>\n<td width=\"120\">User.picUrl<\/td>\n<td width=\"300\">&#8220;&#8221;<\/td>\n<\/tr>\n<tr>\n<td width=\"120\">User.role<\/td>\n<td width=\"300\">Role.CONTACT<\/td>\n<\/tr>\n<tr>\n<td width=\"120\">User.userId<\/td>\n<td width=\"300\">SyncContactsMessage.senderClientId<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<ul>\n<li>On lines 81-85, our newly-minted list of User objects is passed to insertOrUpdateContact(). This method writes the list of Users to the Users table in temi\u2019s SQLite 3 database, completely overwriting temi\u2019s old contacts list in the process.<\/li>\n<\/ul>\n<p>So now that we knew how an ADMIN\u2019s contacts are synced to their temi, how could we leverage that knowledge to add ourselves to temi\u2019s contact list in a discrete fashion? Well, if we could publish a message to temi&#8217;s synccontacts MQTT topic, we could add ourselves as a contact. Although temi does perform a check to make sure that the sender of the message is its ADMIN, it makes the mistake of trusting that the contents of the message accurately reflect the actual sender. In theory, there&#8217;s nothing stopping us from publishing a SyncContactsMessage from one client ID and setting the senderClientId field in the message to a completely different ID \u2013 the ADMIN&#8217;s client ID, for example.<\/p>\n<p>Based on our understanding of how the temi robot parses MQTT requests to sync contacts, we crafted a JSON message that should decode into a valid SyncContactsMessage object and would add our &#8220;attack&#8221; phone to temi&#8217;s contacts:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103746\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/70.png\" alt=\"\" width=\"1430\" height=\"87\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/70.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/70-300x18.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/70-1024x62.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/70-768x47.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/70-205x12.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 70: Our custom SyncContactsMessage in JSON format<\/p>\n<p>This message was crafted using the following rationale:<\/p>\n<ol>\n<li>Objects in JSON are indicated via curly braces (<strong>{}<\/strong>). Since the entire message contents are being converted into a single SyncContactsMessageobject, it follows that the contents should be contained within a pair of curly braces, representing the SyncContactsMessage\u00a0object itself.<\/li>\n<li>A SyncContactsMessagecontains a senderClientId, a string indicating the client ID of the &#8220;sender&#8221; of the message. Thus, we added an element to our object with the key &#8220;senderClientId&#8221; and the string &#8220;060f62296e1ab8d0272b623f2f08f915&#8221; \u2013 the client ID of the temi&#8217;s admin \u2013 as its value.<\/li>\n<li>A SyncContactsMessagealso contains contacts, a list of ContactEntry Thus, we added an element with the key &#8220;contacts&#8221; and a new list as its value. In JSON, lists are indicated via square brackets (<strong>[]<\/strong>).<\/li>\n<li>In our case, the contacts list contains only a single ContactEntry \u2013 one corresponding to the &#8220;attack&#8221; phone, so we added a single pair of curly braces to the list to indicate the contents of this single ContactEntry.<\/li>\n<li>Each ContactEntry contains a clientId. Thus, we added an element to the object with the key &#8220;clientId&#8221; and the string &#8220;fe5d7af42433f0b6fb6875b6d640931b&#8221; \u2013 the client ID of the &#8220;attack&#8221; phone \u2013 as its value.<\/li>\n<li>Each ContactEntry also contains a name. Thus, we added a second element to the object with the key &#8220;name&#8221; and the string &#8220;Test&#8221; as its value. This simply represents the display name for the contact, so we could set it to basically whatever we liked.<\/li>\n<li>As for spacing and newlines, we referred to the <a href=\"https:\/\/github.com\/google\/gson\/blob\/master\/UserGuide.md\">Gson User Guide<\/a>, since that\u2019s the library temi was using to perform JSON serialization\/deserialization. The guide states:<\/li>\n<\/ol>\n<p>\u201cThe default JSON output that is provided by Gson is a compact JSON format. This means that there will not be any whitespace in the output JSON structure. Therefore, there will be no whitespace between field names and its value, object fields, and objects within arrays in the JSON output.&#8221;<\/p>\n<p>As a result, whitespace and newlines have been omitted.<\/p>\n<p>Now that we understood where to publish the request and what the request should look like, the next step was figuring out how to alter the temi phone app to send this request. Since we were aiming for a simple proof-of-concept, we prioritized ease and speed of implementation over robustness or elegance when considering which code to change. To this end, it made sense to leverage the code already present in the app that was dedicated to publishing MQTT messages since it would minimize the amount of code we needed to change and thus reduce the risk of introducing unintended bugs into our altered app \u2013 an ever-present risk when directly modifying an app&#8217;s bytecode. While the phone app publishes many MQTT messages to various topics during its runtime, we decided to try using the phone app&#8217;s video calling functionality. This had the obvious advantage of giving us clear control over when and how often the MQTT message is published, since calls are always initiated by hitting the &#8220;Connect&#8221; or &#8220;Call&#8221; buttons. Ultimately, we decided to leverage InvitationManagerImpl.sendInviteMsg() for this purpose. If we refer back to <u>Figure 39<\/u> and our section \u201cHow are MQTT Call Invite Messages Published?\u201d, the reason for this becomes apparent: sendInviteMsg() has a direct interface to MqttManagerImpl.publish(), the underlying method temi uses to publish arbitrary MQTT messages, while still being specific to the call chain for outgoing video calls. This meant that it would only get executed unless when we manually initiated a call from the app.<\/p>\n<p>Running our altered app resulted in our attack phone being added to temi\u2019s contact list, as shown in the following video:<\/p>\n<p align=\"center\"><iframe loading=\"lazy\" src=\"https:\/\/www.youtube.com\/embed\/cR0NkxBUisA\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<h3><a name=\"Toc45545117\"><\/a>Gaining OWNER Privileges<\/h3>\n<p>All that was left was for us to craft and publish a custom OwnersMessage in order to gain OWNER privileges on our temi. As before, we began by crafting the JSON for the message itself:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103749\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/71.png\" alt=\"\" width=\"1430\" height=\"87\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/71.png 1430w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/71-300x18.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/71-1024x62.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/71-768x47.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/71-205x12.png 205w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 71: Our custom OwnersMessage in JSON format<\/p>\n<p>This message was crafted using the following rationale:<\/p>\n<ol>\n<li>Since the entire message contents are being converted into a single OwnersMessage object, it follows that the contents should be contained within a pair of curly braces, representing the OwnersMessage object itself.<\/li>\n<li>An OwnersMessage contains a request, an object of the OwnersRequest class, and nothing else. Thus, we added an element with the key &#8220;request&#8221; and a new object as its value. The contents of this inner object will become our OwnersRequest.<\/li>\n<li>An OwnersRequest contains a type, an enum specifying the type of request. In our case we want to add an OWNER, so we added an element with the key &#8220;type&#8221; and the string &#8220;OWNERS_ADD&#8221; as its value. As for why we&#8217;re expressing this enum value as a string, it&#8217;s because <a href=\"https:\/\/futurestud.io\/tutorials\/gson-advanced-mapping-of-enums\">this article<\/a> shows that Gson&#8217;s default behavior is to express enum values as strings corresponding to the name of the enum value.<\/li>\n<li>An OwnersRequest also contains ownerIds, a list of strings enumerating the temi contacts the request should apply to. In our case, the list contains only a single ID \u2013 the ID of the &#8220;attack&#8221; phone, which is just the MD5 hash of its phone number.<\/li>\n<li>As before, spaces and newlines have been omitted, per the Gson User Guide.<\/li>\n<\/ol>\n<p>Fortunately, we were able to leverage the code modifications we used to publish our SyncContactsMessage, since the only things changing were the MQTT topic we\u2019re publishing to and the contents of the message.<\/p>\n<p>Our full test consisted of running our first modified app in order to add ourselves to temi\u2019s contact list, followed by our second modified app in order to perform privilege escalation, as shown in the following video:<\/p>\n<p align=\"center\"><iframe loading=\"lazy\" src=\"https:\/\/www.youtube.com\/embed\/56JoHb4bioI\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>The call appeared to be fully functional, and we were able to drive temi around, navigate to its saved locations, and receive its audio\/video feeds. All an attacker needed to make this possible was the phone number of one of temi\u2019s contacts.<\/p>\n<p>This authentication bypass was the final and most impactful vulnerability we discovered in the temi robot, denoted by CVE-2020-16169 and having a CVSS score of 9.4. To better understand how this an auth bypass, let\u2019s compare temi\u2019s privilege management during normal operation:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103752\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/privilege-management.png\" alt=\"\" width=\"1169\" height=\"591\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/privilege-management.png 1169w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/privilege-management-300x152.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/privilege-management-1024x518.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/privilege-management-768x388.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/privilege-management-205x104.png 205w\" sizes=\"auto, (max-width: 1169px) 100vw, 1169px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 72: temi&#8217;s privilege management during normal operation<\/p>\n<p>to how it looks using our modified app:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-103755\" src=\"\/blogs\/wp-content\/uploads\/2020\/07\/modified-app.png\" alt=\"\" width=\"1169\" height=\"451\" srcset=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/modified-app.png 1169w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/modified-app-300x116.png 300w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/modified-app-1024x395.png 1024w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/modified-app-768x296.png 768w, https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/07\/modified-app-205x79.png 205w\" sizes=\"auto, (max-width: 1169px) 100vw, 1169px\" \/><\/p>\n<p style=\"text-align: center;\">Figure 73: temi&#8217;s privilege management with auth bypass<\/p>\n<p>As you can see, although authentication <em>is<\/em> in place for adding OWNERs, it can be circumvented entirely by simply spoofing the MQTT message temi expects to receive from the REST server post-authentication.<\/p>\n<h2><a name=\"Toc45545118\"><\/a>Refining the Exploits<\/h2>\n<p>Once our exploits had the capabilities we initially set out to obtain, we got to work refining them. We created a single modified app that combined all of the MQTT attack vectors we previously outlined: it could intercept calls and send both MQTT messages necessary to obtain OWNER privileges, all with a single button press. Furthermore, we added some additional logic to automatically extract the client IDs required by our custom MQTT messages from the app\u2019s contact list, meaning our app would work on any temi robot. To see all these features in action, please refer to the video below:<\/p>\n<p align=\"center\"><iframe loading=\"lazy\" src=\"https:\/\/www.youtube.com\/embed\/_YKs31sotB0\" width=\"560\" height=\"315\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<h2><a name=\"Toc45545119\"><\/a>Impact<\/h2>\n<p>At this point it would be prudent to take a step back, review the combined capabilities of the exploits we\u2019ve outlined, and consider what impact these might have in a real-world setting.<\/p>\n<p>At the time of discovery, the vulnerabilities in the temi robot meant that an attacker could join any ongoing temi call simply by using a custom Agora app initialized with temi\u2019s hardcoded App ID and iterating over all 900,000 possible channel names \u2013 certainly feasible with modern computing power. This becomes more plausible if one considers that our testing revealed that joining a temi call this way does not notify either of the existing call participants that another user is present, since the apps were only designed with 1-on-1 calling in mind. The fact that the attacker needs no information on the victims makes this attack vector worrisome, but it also means that the attacker has no control over who he or she spies on using this method. Realistically, a malicious actor might use this exploit as a means of reconnaissance \u2013 by collecting data on arbitrary temi users, they could follow up on the more \u201cpromising\u201d ones later with one of the targeted attack vectors. Given temi\u2019s limited adoption by consumers but ramping adoption in industries such as healthcare, the odds are in the attacker\u2019s favor that they stumble upon sensitive information.<\/p>\n<p>Furthermore, if an attacker has a specific victim in mind, they could leverage the lack of per-topic authentication in temi\u2019s MQTT implementation in order to intercept calls between a user and their temi. The plausibility of this particular attack vector is difficult to evaluate. On the one hand, the only information the attacker needs is the victim\u2019s phone number, and telemarketers consistently remind us that this is a very low bar. On the other hand, this exploit\u2019s behavior during our testing proved somewhat inconsistent. At times, the user running the exploit could listen in on the call as a third party with the call participants being none the wiser. At other times, the user running the exploit would disconnect the original caller and take their place. It is easy to imagine scenarios where both of these outcomes are desirable to a malicious actor, and neither bodes well for the privacy of the user being targeted. It is also important to note that although we focused on using this vulnerability to intercept calls, it can also be used to intercept all sorts of notifications intended for another user: contacts being added, when temi goes online\/offline, its current location, etc. While less flashy than intercepting a call, this approach is more discreet and might allow a dedicated attacker to learn a user\u2019s routine, only showing their hand to listen in when it would be most impactful.<\/p>\n<p>Of the possible attack vectors, we believe that the ability to call and control a temi remotely, leveraging the authentication bypass present in temi\u2019s privilege management mechanism, is the most impactful. The bar for this attack vector is arguably even lower than the previous, as the attacker would only need the phone number of <em>any<\/em> of temi\u2019s contacts \u2013 it need not be its admin. In our testing, none of the steps involved in leveraging this exploit notify temi\u2019s admin in any way that something is amiss; they are not notified that the attacker has added themselves to the robot\u2019s contact list nor that they have gained raised privileges. Since this method does not cause temi to ring, an observer would have to see the attacker move temi or have a good look at its touchscreen during the attack to know something nefarious was going on. This level of control and subtlety becomes especially problematic when we consider some of temi\u2019s applications in industry. In April of this year, Robotemi Global Ltd. stepped up production to 1,000 units per month in response to <a href=\"https:\/\/www.calcalistech.com\/ctech\/articles\/0,7340,L-3809114,00.html\">Israel\u2019s Ministries of Defense and Health choosing temi to assist with patients in their COVID-19 wards<\/a>. In South Korea, <a href=\"https:\/\/jewishnews.timesofisrael.com\/how-israeli-health-tech-is-helping-combat-coronavirus\/\">temi sees use in both public places and nursing homes<\/a>, helping to facilitate social distancing. Besides the obvious impact of compromising patients\u2019 sensitive medical information during remote doctor\u2019s visits, the ability to have eyes and ears into a hospital is worrying in its own right. It isn\u2019t difficult to imagine what a malicious agent might do with an overheard network password, access code to a sensitive area, or the location and condition of a person of interest.<\/p>\n<h2><a name=\"Toc45545120\"><\/a>Conclusion<\/h2>\n<p>The findings outlined in this paper highlight the importance of secure coding practices and security auditing for cutting edge technologies, particularly in the IoT space. When an IoT camera evolves into one that can also drive around your home or business and even assist medical patients, the need to properly secure who can access it only becomes more important. While history has demonstrated that any sufficiently complex software is bound to have vulnerabilities, there are many steps we can take to substantially raise the bar for bad actors. In our view, these responsibilities are shared between vendors, consumers, and the security industry as a whole.<\/p>\n<p>First and foremost, vendors can ensure they are employing proper security hygiene when designing products. Often, best practices consist not of novel, out-of-the-box thinking, but rather adopting time-tested guidelines like the <em>principle of least privilege<\/em>. In the case of temi, application of this principle likely would have addressed CVE-2020-16167, which allows clients to subscribe\/publish to topics they have no business accessing. Considerations for security should also extend beyond the development phase, with third-party security auditing being a powerful tool that allows vendors to discover and patch vulnerabilities before the bad guys get ahold of them. Taking vulnerability disclosure seriously and cooperating with the bodies that report them can often net similar benefits, and this is an area Robotemi excelled in.<\/p>\n<p>Consumers of these technologies, at either the individual or enterprise level, also share some of the responsibility. Companies should ideally vet all technologies before widespread adoption, particularly if these technologies have access to customer information. It goes without saying that greater risk warrants greater scrutiny. Individuals, while typically having fewer resources, can also take certain precautions. Placing IoT devices on a VLAN, a virtually segregated subnetwork, reduces the risk that a vulnerability in one device will compromise your entire network. Staying up to date on important vulnerabilities can also help inform consumers\u2019 purchasing decisions, although the presence of vulnerabilities is often less illuminating than if\/how a company chooses to address them.<\/p>\n<p>Finally, we in the security industry also have an obligation towards moving the needle in the realm of security, one that we hope research such as this embodies. One of our goals here at McAfee ATR is to identify and illuminate a broad spectrum of threats in today\u2019s complex and constantly evolving landscape. While we take seriously our obligation to inform vendors of our findings in a timely and responsible fashion, it is only through cooperation that the best results are possible. Our partnership with Robotemi on addressing these vulnerabilities was a perfect example of this. They responded quickly to our private disclosure report, outlined to us their plans for mitigations and an associated timeline, and maintained a dialogue with us throughout the whole process. We even received feedback that they have further emphasized security in their products by approaching all development discussions with a security-first mindset as a result of this disclosure. The ultimate result is a product that is more secure for all who use it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at&#8230;<\/p>\n","protected":false},"author":1084,"featured_media":98531,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[442],"tags":[],"coauthors":[5850],"class_list":["post-103530","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-mcafee-labs"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Call an Exorcist! My Robot\u2019s Possessed! | McAfee Blog<\/title>\n<meta name=\"description\" content=\"Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Call an Exorcist! My Robot\u2019s Possessed! | McAfee Blog\" \/>\n<meta property=\"og:description\" content=\"Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/\" \/>\n<meta property=\"og:site_name\" content=\"McAfee Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/McAfee\/\" \/>\n<meta property=\"article:published_time\" content=\"2020-08-06T04:01:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-07-08T06:23:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1280\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Mark Bereza\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ROPsicle\" \/>\n<meta name=\"twitter:site\" content=\"@McAfee\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Mark Bereza\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"65 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/\"},\"author\":{\"name\":\"Mark Bereza\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#\/schema\/person\/0960bc8b469c17a5b3c781599ae13693\"},\"headline\":\"Call an Exorcist! My Robot\u2019s Possessed!\",\"datePublished\":\"2020-08-06T04:01:06+00:00\",\"dateModified\":\"2024-07-08T06:23:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/\"},\"wordCount\":13015,\"publisher\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg\",\"articleSection\":[\"McAfee Labs\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/\",\"url\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/\",\"name\":\"Call an Exorcist! My Robot\u2019s Possessed! | McAfee Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg\",\"datePublished\":\"2020-08-06T04:01:06+00:00\",\"dateModified\":\"2024-07-08T06:23:00+00:00\",\"description\":\"Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat\",\"breadcrumb\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage\",\"url\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg\",\"contentUrl\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg\",\"width\":1920,\"height\":1280},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Blog\",\"item\":\"https:\/\/www.mcafee.com\/blogs\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Other Blogs\",\"item\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"McAfee Labs\",\"item\":\"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Call an Exorcist! My Robot\u2019s Possessed!\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#website\",\"url\":\"https:\/\/www.mcafee.com\/blogs\/\",\"name\":\"McAfee Blog\",\"description\":\"Internet Security News\",\"publisher\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.mcafee.com\/blogs\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#organization\",\"name\":\"McAfee\",\"url\":\"https:\/\/www.mcafee.com\/blogs\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2023\/02\/mcafee-logo.png\",\"contentUrl\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2023\/02\/mcafee-logo.png\",\"width\":1286,\"height\":336,\"caption\":\"McAfee\"},\"image\":{\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/McAfee\/\",\"https:\/\/x.com\/McAfee\",\"https:\/\/www.linkedin.com\/company\/mcafee\/\",\"https:\/\/www.youtube.com\/McAfee\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#\/schema\/person\/0960bc8b469c17a5b3c781599ae13693\",\"name\":\"Mark Bereza\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.mcafee.com\/blogs\/#\/schema\/person\/image\/76f31245d08b23e60cb3dfa20d1ee6a8\",\"url\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2019\/08\/headshot-96x96.png\",\"contentUrl\":\"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2019\/08\/headshot-96x96.png\",\"caption\":\"Mark Bereza\"},\"description\":\"Mark Bereza is a Security Researcher and recent addition to McAfee's Advanced Threat Research team. An alumnus of Oregon State University's CS systems program, Mark's work has focused on vulnerability discovery and exploit development for embedded systems.\",\"sameAs\":[\"https:\/\/www.linkedin.com\/in\/mark-bereza\/\",\"https:\/\/x.com\/@ROPsicle\"],\"url\":\"https:\/\/www.mcafee.com\/blogs\/author\/mark-bereza\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Call an Exorcist! My Robot\u2019s Possessed! | McAfee Blog","description":"Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"en_US","og_type":"article","og_title":"Call an Exorcist! My Robot\u2019s Possessed! | McAfee Blog","og_description":"Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat","og_url":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/","og_site_name":"McAfee Blog","article_publisher":"https:\/\/www.facebook.com\/McAfee\/","article_published_time":"2020-08-06T04:01:06+00:00","article_modified_time":"2024-07-08T06:23:00+00:00","og_image":[{"width":1920,"height":1280,"url":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg","type":"image\/jpeg"}],"author":"Mark Bereza","twitter_card":"summary_large_image","twitter_creator":"@ROPsicle","twitter_site":"@McAfee","twitter_misc":{"Written by":"Mark Bereza","Est. reading time":"65 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#article","isPartOf":{"@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/"},"author":{"name":"Mark Bereza","@id":"https:\/\/www.mcafee.com\/blogs\/#\/schema\/person\/0960bc8b469c17a5b3c781599ae13693"},"headline":"Call an Exorcist! My Robot\u2019s Possessed!","datePublished":"2020-08-06T04:01:06+00:00","dateModified":"2024-07-08T06:23:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/"},"wordCount":13015,"publisher":{"@id":"https:\/\/www.mcafee.com\/blogs\/#organization"},"image":{"@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage"},"thumbnailUrl":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg","articleSection":["McAfee Labs"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/","url":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/","name":"Call an Exorcist! My Robot\u2019s Possessed! | McAfee Blog","isPartOf":{"@id":"https:\/\/www.mcafee.com\/blogs\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage"},"image":{"@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage"},"thumbnailUrl":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg","datePublished":"2020-08-06T04:01:06+00:00","dateModified":"2024-07-08T06:23:00+00:00","description":"Overview As part of our continued goal of helping developers provide safer products for businesses and consumers, we here at McAfee Advanced Threat","breadcrumb":{"@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#primaryimage","url":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg","contentUrl":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2020\/02\/iStock-954683756-min-2.jpg","width":1920,"height":1280},{"@type":"BreadcrumbList","@id":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/call-an-exorcist-my-robots-possessed\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Blog","item":"https:\/\/www.mcafee.com\/blogs\/"},{"@type":"ListItem","position":2,"name":"Other Blogs","item":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/"},{"@type":"ListItem","position":3,"name":"McAfee Labs","item":"https:\/\/www.mcafee.com\/blogs\/other-blogs\/mcafee-labs\/"},{"@type":"ListItem","position":4,"name":"Call an Exorcist! My Robot\u2019s Possessed!"}]},{"@type":"WebSite","@id":"https:\/\/www.mcafee.com\/blogs\/#website","url":"https:\/\/www.mcafee.com\/blogs\/","name":"McAfee Blog","description":"Internet Security News","publisher":{"@id":"https:\/\/www.mcafee.com\/blogs\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.mcafee.com\/blogs\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.mcafee.com\/blogs\/#organization","name":"McAfee","url":"https:\/\/www.mcafee.com\/blogs\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.mcafee.com\/blogs\/#\/schema\/logo\/image\/","url":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2023\/02\/mcafee-logo.png","contentUrl":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2023\/02\/mcafee-logo.png","width":1286,"height":336,"caption":"McAfee"},"image":{"@id":"https:\/\/www.mcafee.com\/blogs\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/McAfee\/","https:\/\/x.com\/McAfee","https:\/\/www.linkedin.com\/company\/mcafee\/","https:\/\/www.youtube.com\/McAfee"]},{"@type":"Person","@id":"https:\/\/www.mcafee.com\/blogs\/#\/schema\/person\/0960bc8b469c17a5b3c781599ae13693","name":"Mark Bereza","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.mcafee.com\/blogs\/#\/schema\/person\/image\/76f31245d08b23e60cb3dfa20d1ee6a8","url":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2019\/08\/headshot-96x96.png","contentUrl":"https:\/\/www.mcafee.com\/blogs\/wp-content\/uploads\/2019\/08\/headshot-96x96.png","caption":"Mark Bereza"},"description":"Mark Bereza is a Security Researcher and recent addition to McAfee's Advanced Threat Research team. An alumnus of Oregon State University's CS systems program, Mark's work has focused on vulnerability discovery and exploit development for embedded systems.","sameAs":["https:\/\/www.linkedin.com\/in\/mark-bereza\/","https:\/\/x.com\/@ROPsicle"],"url":"https:\/\/www.mcafee.com\/blogs\/author\/mark-bereza\/"}]}},"_links":{"self":[{"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/posts\/103530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/users\/1084"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/comments?post=103530"}],"version-history":[{"count":8,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/posts\/103530\/revisions"}],"predecessor-version":[{"id":196110,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/posts\/103530\/revisions\/196110"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/media\/98531"}],"wp:attachment":[{"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/media?parent=103530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/categories?post=103530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/tags?post=103530"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.mcafee.com\/blogs\/wp-json\/wp\/v2\/coauthors?post=103530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}