Quantcast
Channel: Hakin9 – IT Security Magazine
Viewing all articles
Browse latest Browse all 612

External Understanding: Dissecting APIs inside of IoT devices (Part2)

$
0
0

Introduction

If you have not read Part 1, it is highly suggested that you do so. This article is the second part and continuation of the article `External Understanding: Dissecting APIs inside of IoT devices (Part1)`. For those who have already read that and are here for part 2, let me give you a brief breakdown of where we left off and where we are going. Part 1 of the article gave you a base introduction to our target, objective, and security research and even got into talking about various protocols, control systems, formats, and even data that Apple TV devices send. We finally ended the article talking about DAAP and how DAAP looks over the network and even doing some cool little Google tricks here and there to give us some base documentation! In this part of the article, we will talk about DAAP as a whole, then talk about different protocols used by Apple such as AirPlay and AirTunes. Finally, we’ll go onto how we can better understand the file formats that are given as responses from specific paths visited on these servers running these specific protocols. We will end up dissecting those files manually using specific tools on Linux to better output the format of the files and then write a program in golang to help us download these files and detect specific codes within the servers, basically converting them to English instead of `mstterrr@#$&@#^` as some files will output.

 

 

Continuing section (0x3 — the DAAP protocol and the responses)

Alright, so now we have base information for the bytes, now let’s try the login URL. When we reformat our data with the proper parameters and make the same get request, we are bound to get an error. We know this for two reasons:

  • 1: The server is not responding due to a session not being in place or in-store
  • 2: no authentication parameters have been put in place, which means we did not pass any data.

The reformatted URL looks like the following:

http://10.0.0.96:3689/login?attempts=1

We then get another dump that looks similar with different parameters and codes.

In this image, we see two new codes from the DAAP server and another weird one that seems to be a set of characters indicating some data. Below, like the table above, is a table of the code name, the description, and the byte order in which they responded.

Section 2 (Dissecting values according to their codes)

In the table above were the codes and their descriptions, but one thing we missed was the character set at the end of mlid. These are known as values to the fields (mstt, merr, mlid, mlog) and other various codes from the DAAP server. In order to parse this code, we need to get an understanding of how it works and how we can parse these values. It is important to note that again everything in this scenario, even being dissected, is assumed based on our given scenario and is not to the fullest extent tested and experimented with. Based on unofficial documentation, protocol dissection, and previous research, this is the most likely outcome of the variables and data we have found based on the research. When we look at a hex dump like the one provided above, we can check the two or three tags and understand that these are definitions or abbreviations of specific codes, but how does a program dissect this and automate this request or figure out what happened without raw HTTP status codes? Well, we can simply extract the values of those tags. Each tag or field has its own value hidden a bit deeper within the data as we need to be able to properly dissect this. Below you will find a table of tags followed by their values and a process of doing it.

Each individual code most likely has its own value, but it is really hard to decipher them as some responses may even be clustered in the files and may not be readable in some cases.

Just from this little bit of research and dissection, we can head back to the packet we were looking at and clearly know what the DAAP server is sending and the data that it holds. When the DAAP server sends DMAP-TAGGED data and responses, it is most likely statuses that come from a response being denied or fulfilled. Alright, so now what? We have a decent understanding of the DAAP server and understand the codes and what it does as well as the responses. This can help us when researching further into systems and get us to research more into the protocol. Based on only two packets we found within a capture it sparked an entire search! Can you imagine what a base understanding can do? Well, now that we have the basics of DAAP down and can understand that a majority of Apple’s responses are either PLIST files, binary streams, or tag-based data, we can move on to actually tackling other API endpoints.

 

Getting to know AirPlay/AirTunes

The AirTunes and AirPlay protocols and servers are both quite interesting as they (according to unofficial documentation) contain hundreds of paths that are easy to mine via Google or are just generally known as path brute force. So, we are going to use some of the paths from the official documentation and then explain something about why Apple uses BPLIST files in some responses. So going back again to the table I will show again so you don’t have to go back to Part 1, we see our port numbers for AirPlay and AirTunes.

  • AirTunes → 5000/7000
  • AirPlay → 7100

 

When we did our scan, another cool thing that we saw was that most of these servers can all, or possibly all, be accessed via browser with the right parameters and their responses can be seen as well in plain text or in download form. Before we can continue researching using some basic paths, let's put some base logic together. When it comes to security research, a good chunk of the research can all be through logical thinking where we explore things to our knowledge. When I started looking into and researching IoT devices, the first device I started with was a RokuTV and the main paths I found all had base names to them. For example, server information, apps, applications, app-info, dev-info, device-call, etc., were all logical endpoints that I figured were a good place to start. I then wrote a base program to brute force everything I could think of and eventually found some pretty cool ones. The first service we are going to explore is going to be AirTunes on port 7000, simply because this is where that same logic applied. When I got overly frustrated with Roku because I kept running into dead ends, I tried the same process with the AirPlay service on an Apple TV. I wanted information, just anything, and went to the browser and started spam requesting a bunch of paths, one of them was labeled info and it would prompt for a download like so:

When making a GET request using CURL with no parameters, I got the following output (the same one we have been seeing with file-based streams).

When I looked back at my Wireshark tab, I oddly enough saw nothing from the output of the request even when making it in my browser, which confused me. It wasn’t like I could see the data being downloaded but either way, I was left with a file with the following properties:

Further looking into this file, I was still confused as to what it was, so when looking back at the content type, I started to do research and found out that some servers, especially AirPlay, will encode or store data using the BPLIST file format. So, we will be further dissecting this to better understand how the AirPlay service works.

When we hex dump the file, we see the following giant chunk of data gets spit out that is kind of human readable:

Just based on reading some of the columns, I can infer right away that this server is storing informational settings for the AirPlay server. For example, looking at address 0x000000030 we can see that there is a definition for some sort of flag, which can make me assume that these are configurations or settings stored within the server for information, and if that information is changed then it will change the status of the AirPlay session or the setting within the AirPlay session. At the current state of the hex dump, we might not be able to decipher everything. We can use some tools like plutil to better convert the data to a more readable format, which will allow us to understand what is going on inside of our AirPlay server. The next section will specifically talk about the bare bones and the basics of a BPLIST, such as understanding its data types, arrays, reasons for use, and other various topics.

 

Section 2.5 The BPLIST file format

The Binary Property List File Format is one of the most popular file formats within system services located on Apple devices or servers that may implement protocols that were developed by Apple. This is because BPLIST has a lot of major benefits and can be used in many scenarios and provide better times, which, as discussed before, the scenario might change the status of using BPLIST. But how exactly do these file formats work? Well, in a sense, a bplist file is just a more advanced structure of the PLIST file as it is just a direct binary version of the property list format as they serve the same purpose but store data differently. The binary property list is broken down into four major sections.

  • Head(er): The header of the file is defined with the magic byte list, which is BPLIST. This header can be used to identify the file format, in the case of looking at our hex dump, the magic byte array for BPLIST. The header of the BPLIST files typically starts with the name of the file bplist followed by the version number. In the case of most Apple services, the version that is most commonly used is the first version, which is 00, or in other words bplist00, which makes up that tag.
  • Trailer: The trailer is the metadata section of the file that defines its headers, the offset table, the object table, the number of objects in the obj-table, the offsets at the top of the object and even the size of the offset integers used in the file. This is located at the end of the PLIST file and is always 32 bytes in the side, it starts with a magic number and ends with a byte-sized integer field that specifies the number of bytes used to store integer offsets. In between, there are other fields, such as two fields that indicate the offset to the object table and the number of objects in the table. The offset to the object table is defined as the number of bytes from the beginning of the file to the start of the object table. The number of objects in the table is the total number of objects contained in the file.

Cool! So now we have a base understanding of the parts of the object file, we can better understand how the types work and how the data is represented. The data type table that the BPLIST created is shown below:

This type of representation can be used to better analyze the hex dump that was shown further up. Remember that our hex dump cannot be fully parsed but we can write some examples from eye to eye. Typically, when working with files like BPLIST files, you do not manually parse them as there are frameworks to parse them for you. However, working with it yourself will give you some experience in understanding the file format.

Let's break this down a bit more; below I have provided an example of how we can dissect this file manually given the MAC address example.

  • ¹Breakdown: When we look at the hex dump provided, we can already spot a few things. We can verify that the file is a binary plist file by the header section and that it is version 0 of the BPLIST (the very first version). When looking further we can find and locate the object table, which, in our case, is stored at the trailer section that starts at 0x90 with an object table that starts at the offset 0x1A0. Going further, we can analyze that the object table starts immediately after the file header, and it consists of a sequence of object offsets. Each object offset is an 8-byte integer value that specifies the offset of an object in the file, which is relative to the start of the object table. The hexadecimal value of
  • ²represents our first few bytes of the file showing the header (going back before the table)
  • ³after the object table comes the root object which is an array object, this is defined with The actual object table value is located at offset 0x10 in the hex dump, and it is “08090a0b0c0d0e0f101112131415202b2c2d2e130”. We know that this value represents an array object because it starts with the array object tag “08”, followed by the length of the array “090a”, which is 9 elements, and then the elements of the array. For context, we get this final result “08090a0b0c0d0e0f101112131415202b2c2d2e130” from

Extracting and converting this data is kind of a pain, for the scope of this article we will not be manually dissecting everything when we can use plistutils to better help us out with this. The PlistUtils suite of tools can help us convert binary PLIST files into regular PLIST files. To do so, make sure you have the suite installed, if you do not, you can run the command shown below:

Once installed, we can take out the input file and run the following command:

We will see (in my case) the following output in the XML file when we open the document up.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>macAddress</key>
<string>FF:FF:50:DF:6F:96</string>
<key>statusFlags</key>
<integer>68</integer>
<key>pi</key>
<string>d03045bd-a73a-4998-ad8d-c87a44c97922</string>
<key>deviceID</key>
<string>F4:F9:51:DF:6F:97</string>
<key>keepAliveSendStatsAsBody</key>
<true/>
<key>vv
</key>
<integer>2</integer>
<key>audioLatencies</key>
<array>
<dict>
<key>type</key>
<integer>100</integer>
<key>inputLatencyMicros</key>
<integer>0</integer>
<key>audioType</key>
<string>default</string>
<key>outputLatencyMicros</key>
<integer>0</integer>
</dict>
<dict>
<key>type</key>
<integer>101</integer>
<key>inputLatencyMicros</key>
<integer>0</integer>
<key>audioType</key>
<string>default</string>
<key>outputLatencyMicros</key>
<integer>0</integer>
</dict>
</array>
<key>audioFormats</key>
<array>
<dict>
<key>audioOutputFormats</key>
<integer>67108860</integer>
<key>type</key>
<integer>100</integer>
<key>audioInputFormats</key>
<integer>67108860</integer>
</dict>
<dict>
<key>audioOutputFormats</key>
<integer>67108860</integer>
<key>type</key>
<integer>101</integer>
<key>audioInputFormats</key>
<integer>67108860</integer>
</dict></array>
<key>pk</key>
<data>
iBvMMk6AIeeAznq5FLAm48BLHcclUD/5ejPncvbTtHQ=
</data>
<key>model</key>
<string>AppleTV3,2</string>
<key>features</key>
<integer>130367356919</integer>
<key>name</key>
<string>Living Room Apple TV</string>
<key>keepAliveLowPower</key>
<true/>
<key>sourceVersion</key>
<string>220.68</string>
</dict>
</plist>

Right away, we can see this gives us a good amount of information and was much easier than manually converting everything. What does this information tell us? Well, it tells us the audio import and output formats that AirPlay is supporting and also gives us more information on the features and other various data that AirPlay is actually going to need for its settings. When working or configuring AirPlay in the future, we may see these settings change as they are also used by external devices to grab service-based information. This is helpful when digging a bit deeper into the protocol and understanding how AirPlay as a service works.

 

Continuing onto section 2 (getting to know AirPlay)

Going back to the previous statement about how I found that BPLIST file, we may also find similar PLIST files on the AirTunes server but rather see it in regular plist format. When we make a request in our browser to 7000/server-info, we may see the following result.

Let's now use the same logic that we used to get the info file to grab server info. If we go to port 7000, do you think typing playback-info will do anything? The only thing we can do is check. When we type that full address in our browser, we also come across another relative output here:

This is quite confusing at first but if we think about it for a second, in my current case, I am accessing these services without an active session on their AirPlay, AirTunes or DAAP server. The only factor is that they are online and that is it. When working logically like this to produce a specific outcome, which, in our case, is just physically brute forcing paths we come up with, we may most likely run into stuff like this, but we need to think more logically and try to create sessions and experiment with different methods to see if the output changes. All of this information can be used for various reasons and even checking Wireshark logs after making a request, maybe there is something that was found on the wire that we did not physically see in terms of output from the server. I know this is a weird note to end this section on but using your own thinking and processing to better explore protocols is how you can get a specific device or service to spit out information that can aid you in your research. Just based on what we did with AirPlay we can now do tons of research using the data we found as prime queries in our searches!

 

Now What?

So far, this article has given you a base introduction to how to explore the API’s of AppleTV and various services that Apple uses such as the AirPlay service and the AirTunes service. Knowing and understanding these endpoints while also being able to understand deeper into files, you can expand your logic when it comes to exploring these systems and, given that we have explored some neat APIs, you are only a few bits away from further understanding how to scan it better. There was one thing left that we forgot to do which was to use the go programming language. The one thing that felt good to add here was to build a program that can open and verify a file response from the server, giving you a base idea of custom implementation. The automation process of things, once understanding how they work, makes it a lot easier than trying to find some scrappy library from 16 years ago on GitHub written in perl, such as the one below that implements the AirPlay authentication.

Simple 100–200 line perl program representing authentication steps externally to the AirPlay and AirTunes services.

Our next section will teach us how to verify files on specific endpoints, how to work with go to automate those responses while also being able to tell the program to do the bare minimum when it comes to file parsing.

 

Working with Golang

The Go programming language is by far one of the closest to most popular languages for the cyber security world and each day it keeps getting more and more traction. For our specific case, golang comes in handy when working to parse data in files, working with responses, sending requests, and saving the data to an output file, while also being able to write these programs easily without any need for a third-party library. When it comes to cyber security and working to understand a system, it can take several months. Just do base research on that system and then design a program to work with it, and in most cases, you will not be able to use a third party library if you are the one discovering or manually reversing the protocol, framework, service, and application. We are going to be using Go not only because it is a pretty fast language but also due to its wide range of standard libraries that allow us to work efficiently with byte streams and HTTP responses! Below you will find a list of steps to help you understand this program.

  • The Goal: The goal of this program will be to teach you how to automate specific requests and tasks with Go based on limited knowledge or even intermediate knowledge about a system and protocol. We will be using two endpoints /info and /login?attempts=1 one which returned a BPLIST file and the other which resulted in an error from the DAAP server. The goal of this is to write a program that supports and can work with two separate file responses from the servers.
  • The Program Design: The design of this program is very minimal as we will have to do a few primary things. The code we will produce will mainly work around verifying files, defining constant data, defining types and sections we were able to dissect manually, and being able to depend on our knowledge to define the server error codes and messages. This means that we will have a few functions that make requests but because these are just base requests, we will not need to do more-advanced requests because, remember, the goal of the program is to understand how to automate specific processes so we will not go into the protocol and algorithmic implementation for AirPlay verification and spinning up a bunch of sessions.

 

Walking through the source code and development

Now that we have a general layout, we can continue to develop our program. This section will be split up using bullet points to split sections into the code, which goes from imports and variables to maps to helper functions, to validation and finally hitting the main function.

 

  • Variables → Our program will use variables a lot to define codes like MSTT, MERR, MLOG, etc., so we need to convert strings into bytes and then organize them in hexadecimal format. We also need to define two content types, the content type the DAAP server is expected to respond with, which is DAAP-TAGGED, and the content type the AirPlay server is expected to respond with, which is binary-plist. We will also define file names, which are constants. These file names will be used to save the output of DAAP and AirPlay to the local directory in which the program is running so the other functions can open the file and create byte streams to validate data.

We first import all the libraries we need, shown in the following table:

When we define the constants, we define the filenames where it’s easy to understand what response will fit into what file. We are then going to define our codes. Say we have the following codes in a list STR[“mstt”, “merr”, “mstt”, “mlog”, “mlidr”]. We need to be able to take these strings and convert them to their byte representations. To do so, we can call the variable name followed by the []byte() conversion method in Go. This will not output hexadecimal directly in the format we need but it will output the decimal form. A small program shown below shows this.

When we get the codes, we need to transform - all we need to do is separate each value by 2 and go on to add 0x behind them like so.

With this list complete, we can now categorize our data and define our variables.

 

  • Function and Code MAPS: The maps within the program will help us compare codes and output messages. Instead of chaining if-else statements, or using switch case statements, we can just use a map to run functions and to output codes.

 

This code is easier than it looks! The first map we start off with is known as the code message map and will compare codes and return a definition. The %s used within the string will allow us to show the user the index of the byte array in which it was found. This allows for a much more verbose output! The next map is where things get a bit wacky. When we make the requests, we want to be able to filter and check for data based on the response we got for the content type of the data. When these content types are verified and the content of the request is saved to the file, we use this map to then parse the files based on their data. A function is called and run when the map is called, which runs each individual step it needs to, based on the file or content type it is loading. Dissecting this further, if our map comes across the value of the BPLIST content type, it calls a function that will pre-process the file and load the data into a validator (more on this after the maps and sub-functions are declared).

When we come across DMAP-tagged data we open the file and create a buffer for reading the data and read the data into the buffer and check if there is an error. If not, we check the byte array and use another sub-function that will try to find the byte or hexadecimal tags in the byte array. We will go over this function in a second.

  • Helper Functions: Helping functions are functions that are designed to help out the program and, in our case, they are extra functions that help with specific functions or outputs. We have three sub-functions, one that checks errors and the other one that finds a specific byte list in a byte array. The final function makes a GET request to a given URL and checks the content type the server responded with and returns two variables - the body of the response, or general response data, and a Boolean value. If the content type matched the one you were looking for then you were good to go; if not, it would return false and the program function in main that called this function would exit. These functions are shown below. The check errs function will not be talked about because it simply checks an error and logs a fatal if the error was not empty.

These functions for anyone who is not new to Go can understand this; the GET request function uses the http.client to set a timeout for the host but also starts a new request and sets a user agent header. Doing this allows us to better specify who we are rather than the possibility of it being blocked. Once it makes the requests, it checks the header keys for a content type and if the array is not empty, it will grab the first value or first content type response in the array and then check if it was the expected type. If it was then we read the response body and continue, if not we make a debug statement and return nil and false, which means it failed.

 

We have a secondary function called SearchByListIDX to search for the byte sequences corresponding to the given sequences or, in our case, code sequences. The function iterates over the byte array and compares each substring of the same length as the search sequence to the sequence. If a match is found, the function returns the index of the start of the match. If no match is found, the function returns -1, which means it failed. This function is shown below.

  • BPLIST Parsing: The one thing that seems like it would be a pain really is not that much of a pain to explain. Remember when we talked earlier about BPLIST parsing and how understanding specific systems and the internals of files is good for the future? Well, this is one of those cases, understanding the structure of a BPLIST file will help us here. We will not be making our own tool to parse the values, but we will make one to verify the header and use the binary package from golang’s standard library to pack and buffer the data. The following code snippet shows this, which has two functions in it, one that validates the file and one that processes the file.

This code should be decent to read but let us dissect it by section. Before we can verify the file as a BPLIST file, we need to ensure that we can create a byte array of the contents of the file. To do this, we create a function called Pre_Process_File that takes an argument called Data as a pointer to os.File. The *os.File will come from a function that opens the file and loads it properly. We need to make the buffer of the byte array the size of the file so we can properly tell the buffer to read the binary data and create a proper reader that will be used later. The function returns a value of type *bytes.Reader, which comes from bytes.NewReader to create a new reader for the byte array we made. Concluding this, the Pre_Process_File function returns the necessary reader that will be read by another function known as ValidateFile, which is shown below.

This function is a bit easier to understand. Basically, it starts creating a variable named head. Remember, we are trying to verify the files type and to verify it is a BPLIST file so we can use the previous definitions to figure out what exactly it does. When we make the byte array you may notice that the header only takes a size of 8 which is due to bplist00 being of length 8. When we take the byte array and convert it to a string, we then create a conditional statement to check if the string result of the byte array matched the string bplist00 since we are trying to look for that specific file type and version.

 

  • Main Functions: Our main function is a bit easier to understand because it just requires one single argument and then will fill in the requests and start the tests.

As you can see, the function declares a variable named host with a value of the operating system’s arguments with an index of 1. The reason is that when we call os.args with Go is because the filename will always be the first argument in the argument array, [0]. To prevent parsing the argument, we can index it by the second argument in the argument array, which is 1. We parse the data and format the host names with the URLs and call the map with the result of the content types that we found if x or byte[] was not empty.

 

Running this program

When we run this program, if our device is on, we can expect a few things.

  • 1: Our program could fail and not read everything.
  • 2: Our program can go as expected to read the file data and verify every response from the endpoints.

 

So running the command `go run main.go 10.0.0.96` (replace that address with the address of your Apple TV), we get the following output:

Awesome! It works! Now, when working through this output, we know and can tell it is a bit messy and could use a bit of a cleanup. However, the point of the data is to ensure everything worked properly in the program, which it did! It was able to grab the response of the endpoints and properly work with the responses and data we defined. Sometimes, the servers, as discussed, may possibly have a firewall that will block a specific host from accessing that endpoint after so many tries. During the testing of this program, I ended up actually ruining the program so much that the DAAP server would not respond with an x-dmap-tagged response and would return a response saying `100` in HTML format while also giving an error code saying the service is unavailable. This may mean that the service is vulnerable, or something could be making us get blocked. When we start getting server-side errors like this, it may be important to use other discovery methods to more or less explore vulnerabilities in the system and to better see and dig deeper into how the protection systems work. The result of the server’s response could have been again a multitude of things.

  • 1: We could be sending too many requests using the HTTP client from Go at once
  • 2: We could be sending invalid requests that the server is not supposed to process but processes anyway
  • 3: We could be getting blocked by the firewall or server’s internal protection systems

 

There are multiple reasons as to why this can happen, otherwise, our program seems to be working just fine. If we navigate back to the Wireshark application where we were looking for DAAP, we should be able to filter by HTTP and DAAP and continue to see our requests sent by the program. When inspecting these requests, it is important to make sure that we did see the proper response that our program parsed in Wireshark, which we should see and should be the exact same response in hex format.

And as we imagined, we did see the proper response in the capture! This means our program was successfully working and was not breaking or glitching out!

 

Conclusion & Summary

Wow! What a ride, hahaha! If you have gone this far into the article, or the sections this article was split into, I first want to congratulate you as you are one more step to deeper understanding systems on a nicer level! To sum up, we learned about the base and very bare bones of how systems work and cover why security research should go deeper into understanding how the system worked. After talking about Apple as a product, why we chose this topic, and going over various terms, we started by digging into the product and its systems by trying to understand how it works from the outside. We then dug into protocols, services, and how the services work and verified the different types of protocols and file types. We then finally learned how AirPlay sort of works with its responses and goes forward into dissecting the BPLIST file format which was an interesting response from one of the endpoints on the AirPlay service! In concluding this article, security research is very important but being able to properly dissect and learn about a device, its services, and its internal workings are the first step during security research! When conducting security research, it is also important to not just focus on one section or one specific protocol during the research process as being able to get a base understanding of ALL of the system services can help you better understand the device, especially when digging into one specific surface. In the case of Apple TV devices, some devices are a bit more frustrating to research due to how secure they are, closed-source they are, or how private their protocols may be. Despite this con to security research, it is important that we also exercise our mind by taking out more challenging targets and if we can’t get to that point, then maybe we can go back to the drawing board, learn a bit more, and come back later on!


Viewing all articles
Browse latest Browse all 612

Trending Articles