Saturday, March 30, 2013

TiVo App - Overview & Conclusion

So I have spent quite a bit of time, and I have a very good understanding of how the TiVo Stream communicates with the application, but unfortunately I am not any closer to getting the TiVo stream to work on my android or PC devices.

Overview of architecture



However, I can't solve the problem in the iOS App


I cannot extract the decryption algorithm for the playlist items, I cannot debug the applications streaming functionality, and I cannot override any code in the whitebox.

My list of clever ideas has run out...for the iOS App.

Next: Understanding how the TiVo Stream hardware works




Tivo App - Debugger Checks

I have accepted I will never be able to take the TiVo Stream's whiteboxkey and the TivoCrypt and calculate the actual decryption key for a stream, but maybe I can simply reuse the white-box library to use the iPod Touch as a "calculation server" of sorts?

Using GDB to capture Key

Before I try to make a key calculation server, I need to find a way to make the program spit out the key.  I fired up my GDB in a remote terminal and attached to the "Quicksilver" application.  I followed the app along but everytime it started streaming the application would crash with a memory overflow or other random error.

I recalled seeing some code earlier involving "_getpid" and "_sysctl", I thought it was strange that the application would be getting a process ID of another process.  Doing some research I found out that there is a snippet of code that involves both of these commands that is used to determine of the current process is being run in a debugger...


This method involves getting the process ID of the running process and then running sysctl on it and looking for the P_TRACED flag set on processInfo.p_flag.

Looking in the code for this type of code, I stumbled upon a fantastic amount of debugger checks in place:


I spent a few hours trying to modify the code to make the P_TRACED flag never be set, but I quickly realized they had some sort of check in place to see if you had modified code in specific locations (probably inside the whitebox).

At this point, I cannot extract the decryption algorithm, I cannot debug, and I cannot override any code in the whitebox.

TiVo App - TivoProtocolhandler."compareURLData:withdata" method

As we saw earlier, the TivoProtocolHandler."compareURLData:withdata" method takes two arguments, the first being the tivocrypt:// folder converted from hex to bytes, and the second being the whitebox key that was retrieved from the TiVo stream via the "key.scbin" request.

TivoProtocolhandler."compareURLData:withData" analysis


Pulling up this method in IDA Pro...


Wow. That is definitely a white-box obfuscated piece of code.  I tried to decypher it but they start pushing data into random variables and it just becomes impossible to test without a debugger.


At this point, I have to accept the fact that the iOS device never exposes the decryption key, and the algorithm & code to calculate the actual key is so convoluted it would take months to even get close to what would probably be an incorrect answer (there is no way of even knowing if you are close).

I started to think...if I can have my iPod Touch on all the time and publicly expose this method, I could just send in the two arguments (via HTTP) and have it spit out the key...

TiVo App - tivocrypt Schema

We saw that the playlist.m3u8 that is streamed to the iOS device uses what Apple coins as "HTTP Live Streaming".  More specifically, there is a line in the playlist that specifies that the content is encrypted and where its decryption key is stored...

#EXT-X-KEY:METHOD=AES-128,URI="tivocrypt:///29919494ac3b53ab93ba79b17cd06819"

Custom "tivocrypt" Scheme

So I know that the iOS will attempt to hit that URL to retrieve the key, but where is the code that handles that?  And can we extract the key programmatically?

I found a location where the "tivocrypt" string was used on a URL in TiVoProtocolHandler."startLoading"(), this sounds exactly like what we want.


I spent quite a bit of time understanding what this method does, my pseudo code:
  1. Read the incoming request URLs absolute string ("tivocrypt:///{32-hex characters}"), grab the data to the right of "tivoCrypt://" (This should come from the iphone internally)
  2. Split the "/{32-hex characters}" on "/", grab the 2nd string in the array
  3. Loops over the string, converting it to a 16-byte binary called "CompareUrlData"
  4. Call this["compareURLData:withData:"]( CompareUrlData, WithData) to get a new Data object, this new object is our AES-128 key for decrypting video...
  5. Create NsUrlResponse with a NsUrlResponse["initWithURL:MIMEType:expectedContentLength:textEncodingName:"](this["request"]()["URL"]();, "application/octet-stream", Content["length"](), ???); (this should be sent back to the iphone internally)
  6. Have this["client"] (URLProtocolClient) fire off its events
So it is clear, "compareURLData:withData" is taking in some data and outputting our 16-byte key.  Looking at the actual code, the first parameter is the tivocrypt folder name converted from 32-hex characters to a 16-byte value.  But the second argument?

Well...that would be the White Box Key we got from the TiVo Stream earlier!

So, we know both arguments of "compareURLData:withdata", hopefully its just a simple decryption technique...

TiVo App - Streaming .m3u8 playlists

I had been curious how the actual Tivo Stream was .... streaming... the video to the iPhone, in looking at some logs that run during an actual streaming session I found something interesting:

Mar 30 22:55:13 XXXXX-iPod Quicksilver[1286] <Warning>: Preflighter: Transcoder is happy to stream
Mar 30 22:55:13 XXXXX-iPod Quicksilver[1286] <Warning>: TiVoStreamPreflighter: prereqs:2
Mar 30 22:55:13 XXXXX-iPod Quicksilver[1286] <Warning>: backgroundFetchTranscoderInfo done for XXXXXXXXXXXXXX!
Mar 30 22:55:13 XXXXX-iPod tccd[1299] <Notice>: MS:Notice: Installing: (null) [tccd] (793.00)
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: Response for passkey session request: 200, error: (null)
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: TiVoStreamPreflighter: Pass Key success
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: TiVoStreamPreflighter: prereqs:0
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: urlForMFSID:forTSN: string is http://10.0.0.109:49152/live-streaming/{TIVO_GUID}/10160.m3u8
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: Creating TiVoViewerViewController with url=http://10.0.0.109:49152/live-streaming/{TIVO_GUID}/10160.m3u8
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: Displaying status text: Loading Show
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: TiVoViewer: setURL:http://10.0.0.109:49152/live-streaming/{TIVO_GUID}/10160.m3u8
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: removeTimeObserver: self.player=0x0, pat.player=0x0, pat.token=0x0
Mar 30 22:55:14 XXXXX-iPod Quicksilver[1286] <Warning>: Sending POST request to http://10.0.0.109:49152/sysinfo/control?config=session&action=releaseclient

Retrieving m3u8 file

I grabbed the url from the log and retrieved its content in my browser, it contained the following:

#EXTM3U
#EXT-X-TIVO-CONTEXT:7
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8431425,CODECS="avc1.77.31,mp4a.40.2",RESOLUTION=1280x720,TIVOQUAL=BEST
10160/7/2240/playlist.m3u8
#EXT-X-ENDLIST

This file looks like it holds different quality streams, each with their own playlist.m3u8 file.

Retrieving playlist.m3u8 file

Following the path in the previous .m3u8 file I grabbed the playlist.m3u8, it contained the following:
#EXTM3U
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="tivocrypt:///29919494ac3b53ab93ba79b17cd06819"
#EXTINF:2,
0
#EXTINF:2,
1
#EXTINF:2,
2
#EXTINF:2,
3
(I truncated the output, it has many parts)

This is VERY interesting!  It appears that the TiVo Stream sends the data in small pieces specified in playlist.m3u8, encrypted with aes-128.

This appears to be called "HTTP Live Streaming" by Apple in iOS, "HLS" for short: https://developer.apple.com/library/ios/#technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238

Given what I have read about the m3u8 format, it appears that iOS will attempt to hit the given url and it will expect a 128-bit AES key in response to decrypt each file...

Dissecting the Whitebox Key returned by the TiVo Stream

We were able to successfully have the TiVo stream return a whiteboxkey, but what data IS the white box key?

White-box key output

It is important to note that the white-box key changes every X minutes, it is NOT a constant.

Here is the contents of the file:


Well, this is certainly not an AES-128 key.  It is some convoluted key that is probably consumed by a white-box cryptography object.

The header "SKBW" is quite interesting, upon googling "SKB" I found a company called "white cryption" (now cryptanium) that offers a "Secure Key Box" white box solution.  They offer a library for iOS, I imagine this is what TiVo used.  

Product Overview

SKB is a C/C++ library that provides an extensive set of high-level classes and methods for working with the most popular cryptographic algorithms. The library’s unique white-box implementation is specifically designed to hide and protect cryptographic keys at all times. In SKB, keys are always encrypted and cryptographic algorithms operate directly with encrypted keys.
  • Cryptographic keys are always encrypted
  • Diversified code and data
  • Robust white-box cryptography implementation
  • Watermarked program code
  • Security is inseparable from the program code
  • Safe storage of cryptographic keys

It is interesting to note that the parent company of "white cryption" is "intertrust", who has been in the news for suing numerous large companies recently (including HTC, Apple).  They also have a patent application for "Obfuscation techniques for enhacing software security": http://www.google.com/patents/US6668325

I have to accept the reality, I do not have the time or resources to decrypt this white-box cryptography engine.  But do I even have to? What is the white box key even used to decrypt?


Using ssdata.p12 to retrieve our white-box key

Now that we have found out where ccdata was used, let's find out where ssdata.p12 was used.

ssdata.p12 usage

As we saw earlier, the Transcoder "didReceiveAuthenticationChallenge" delegate method was what used the ssdata, but what is the NSUrl that was called that returned this response?

I suspect it is the request made in "fetchWhiteBoxKey:withPort" which accesses "https:/{TIVO_STREAM_IP}:49151/live-streaming/XX_DUMMY_TSN_XX/key.scbin?uuid={UNIQUE_IDENTIFIER}".

Knowing that I could not successfully hit this URL before, I am almost certain that the issue was that I did not have the ssdata.p12 client certificate, so the Tivo Stream was ignoring me.

Converting ssdata.p12 into Java keystore

keytool -importkeystore -srckeystore ssdata.p12 -srcstoretype pkcs12 -srcstorepass WiReKYd6bEDu -destkeystore ssdata.jks -deststoretype jks -deststorepass "changeit"

Writing the program to retrieve our white box key

Let me write a quick application to test this theory:

import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.io.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;

import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import java.security.SecureRandom;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;

import java.util.Enumeration;
public class TestHTTP{
   public static void main(String[] args) throws Exception
   {
        new TestHTTP().testIt();
   }
   private void testIt() throws Exception {
      String https_url = "https://10.0.0.109:49151/live-streaming/XX_DUMMY_TSN_XX/key.scbin?uuid=001122334455667788";
      URL url;
      try {
 
KeyStore ks = this.getKeyStore();
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "WiReKYd6bEDu".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
// Custom Trust manager that lets anything in!
X509TrustManager easyTrustManager = new X509TrustManager() {

public void checkClientTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
// Oh, I am easy!
}

public void checkServerTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
// Oh, I am easy!
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}

};
// Bypass validation of authorities, this is what the iphone app does
TrustManager[] tms = new TrustManager[] {easyTrustManager};

        SSLContext sslContext = null;
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kms, tms, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
// Since the alias is "1" it will not match " 10.0.0.109" unless we override the hostname verifier
HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
 
public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {

System.out.println("Warning: URL Host: " + hostname + " vs. " + sslSession.getPeerHost());
return true;
}
}
);
        
    url = new URL(https_url);
    HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
 
    //dumpl all cert info
    print_https_cert(con);
    //dump all the content
    print_content(con);
      } catch (MalformedURLException e) {
    e.printStackTrace();
      } catch (IOException e) {
    e.printStackTrace();
      }
   }
   
   private KeyStore getKeyStore() throws Exception {

KeyStore ks = KeyStore.getInstance("JKS");

    java.io.FileInputStream fis = new java.io.FileInputStream("ssdata.jks");
    ks.load(fis, "changeit".toCharArray());
    fis.close();
return ks;
   }
   private void print_https_cert(HttpsURLConnection con){
    if(con!=null){
      try {
System.out.println("Response Code : " + con.getResponseCode());
System.out.println("Cipher Suite : " + con.getCipherSuite());
System.out.println("\n");
Certificate[] certs = con.getServerCertificates();
for(Certificate cert : certs){
  System.out.println("Cert Type : " + cert.getType());
  System.out.println("Cert Hash Code : " + cert.hashCode());
  System.out.println("Cert Public Key Algorithm : " + cert.getPublicKey().getAlgorithm());
  System.out.println("Cert Public Key Format : " + cert.getPublicKey().getFormat());
  System.out.println("\n");
}
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
     }
   }
   private void print_content(HttpsURLConnection con){
if(con!=null){
try {
InputStream inputStream  = con.getInputStream();
OutputStream out = new FileOutputStream(new File("whiteBox.key"));
int read = 0;
byte[] bytes = new byte[1024];
 
while ((read = inputStream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
 
inputStream.close();
out.flush();
out.close();
 
} catch (IOException e) {
  e.printStackTrace();
}
   
}

}

Running the program

D:\Projects\TivoStream\Program\RetrieveWhiteBoxKey>java TestHTTP
Warning: URL Host: 10.0.0.109 vs. 10.0.0.109
Response Code : 200
Cipher Suite : SSL_RSA_WITH_RC4_128_SHA

Cert Type : X.509
Cert Hash Code : 1681350
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

It worked! The TiVo stream stopped ignoring us and sent us back a 200 code and populated our "whiteBox.key" file with 149 bytes.

But what is in this file??




Using cdata.p12 to find TiVo's

Now that we have the cdata.p12 password, we can extract it's contents.  Inside is a client certificate that is used to communicate with the TiVo boxes when scanning the network looking for TiVo boxes.

Convert cdata.p12 file to Java Keystore file

keytool -importkeystore -srckeystore cdata.p12 -srcstoretype pkcs12 -srcstorepass cxWmcQ03ukPV -destkeystore cdata.jks -deststoretype jks -deststorepass "changeit"

 Write a program to extract the TiVo host SSL certificate using our client certificate:


import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.io.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;

import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import java.security.SecureRandom;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLSession;
import java.io.OutputStream;

import java.util.Enumeration;
public class Scanner{
   public static void main(String[] args) throws Exception
   {
ScanIp("10.0.0.","141");
   }
   private static void ScanIp(String Mask,int Ip) throws Exception {
try {
System.out.println("Scanning "+ Mask + Integer.toString(Ip));
KeyStore ks = getKeyStore();
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "cxWmcQ03ukPV".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
// Custom Trust manager that lets anything in
X509TrustManager bypassTrustManager = new X509TrustManager() {

public void checkClientTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
}

public void checkServerTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}

};
TrustManager[] tms = new TrustManager[] {bypassTrustManager};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, tms, new SecureRandom());
SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(Mask + Integer.toString(Ip), 443);
sslSocket.setSoTimeout(100); // 100 ms
sslSocket.getInputStream();
//sslSocket.startHandshake();
System.out.println("Found a TiVo!");
SSLSession session = sslSocket.getSession();
java.security.cert.Certificate[] servercerts = session.getPeerCertificates();
for (int i = 0; i < servercerts.length; i++) {
System.out.print("-----BEGIN CERTIFICATE-----\n");
System.out.print(new sun.misc.BASE64Encoder().encode(servercerts[i].getEncoded()));
System.out.print("\n-----END CERTIFICATE-----\n");
}
sslSocket.close();
} catch (Exception e) {
System.out.println("No endpoint found");
e.printStackTrace();
 
   }
   
   private static KeyStore getKeyStore() throws Exception {

KeyStore ks = KeyStore.getInstance("JKS");

    java.io.FileInputStream fis = new java.io.FileInputStream("cdata.jks");
    ks.load(fis, "changeit".toCharArray());
    fis.close();
return ks;
   }
}

If given a TiVo IP, the program will output the base64 host certificate.  This will contain the TSN id described in the earlier post about IP Scanning which will allow you to identify the model of the TiVo box.

TiVo App - Extracting p12 passwords

Given the obfuscation used in SecurityUtils."dateTransform:other"(), we must take a different approach

Finding a way to extract the P12 Passwords


Given the high amount of logging in the application, I figured I could divert the key that is extract from the DateTransform method to a NSLog command and view the password via the "iPhone Configuration Utility".

Looking at the "extractClientCertInfo_withDateInfo_withSecIdentity_withClientCerts__" method, I see that there is an NSLog for an error case: "Client certification identity extraction error %d"


Now we simply need to divert the decrypted key to the NSLog...

Patching the application to output the cdata.p12 password...


NOTE: TCPConnection.UseSSL() uses cdata and it has its own cdata.p12 file with its own crypto info
; ISSUE: cdata.p12 uses a passphrase determined by SUtils."dateTransform:other:"("\x02\\|X\x02u\x1B\x06\x14O{c","a$+5")
; Advanced encryption algorithm used in dateTransform:other, need to find a way to dump it in a debugger or in log
; The key is passed into extractClientCertInfo_withDateInfo_withSecIdentity_withClientCerts__ is a good spot
; R2 = "cdata";
; R3 = Passphrase, put into R10, then into R0 before calling sub_1104C4
; Need to make later log "Client certification identity extraction error %d" output this data
Step 1: Open Quicksilver in hex editor
Step 2: Find ASCII Text "Client certification identity extraction error %d" and change the "%d" to "%@"
Step 3: Find the "MOV R0, R10" (0x10F5EC) that sends the passphrase into sub_1104c4, change it to "MOV R4, R10"
Find:
0x10F5EC MOV R0, R10 0x5046 (reverse endian)
; MOV from R8-15 to R0-7 is MOV Rd, Hs, Op=10, H1=0, H2=1
Hi Register Opartions OP H1  H2 R10 (relative to R8) R0 (relative to R0)
-------------------------------------------------------------------------------------
010001 10 0 1 010 000 EXISTING
46    |   50
-------------------------------------------------------------------------------------
010001 10 0 1 010 100 NEW
46    |   54
-------------------------------------------------------------------------------------
Replace with:
0x10F5EC MOV R4, R10 0x5446 (reverse endian)
Step 4: Find the "BL sub_1104c4 [10F4C4]" (10F5F2) [-302] and change it to "BL 10F610"
Find:
0x10F5F2 BL sub_1104c4 0xFFF7 67FF
F7FF = 11110 11111111111 = High offset of 111111111111 000000000000
FF67 = 11111 11101100111 = Low offset of  000000000000 111011001110
Combining them = 111111111111 111011001110 = -306
The branch offset must take account of the prefetch operation, which causes the PC
to be 1 word (4 bytes) ahead of the current instruction
; Long branch with link - 1111, 2 instructions will be made, the first with the higher half of the offset
;         the second with the lower half. The lowest bit of 0 is assumed to be 0
|  KEY  | Low Offset | Half of Offset
-------------------------------------------------------------------------------------
  1111 | 0 | 11111111111 EXISTING
  1111 | 1 | 11101100111 EXISTING
-------------------------------------------------------------------------------------
  1111 | 0 | 00000000000 NEW
  1111 | 1 | 00000001101 NEW
-------------------------------------------------------------------------------------

Replace with:
Current pointer = 10F5F2
Desired Pointer = 10F610
Offset = 0x1E (d30)
Subtract 4 because of prefetch = 1A
Binary representation = 11010
High Offset = 00000000000
Low Offset  = 00000001101
First Jump = 1111 0 00000000000 = 0xF000
Second Jump = 1111 1 00000001101 = 0xF80D
0x10F5F2 BL 10F610 0x00F0 0DF8
Step 5: Run program, try to connect to Tivo on local network.  Once it fails, look through NSLog for our key
Output: "Client certification identity extraction error cxWmcQ03ukPV"
WE HAVE OUR KEY!  This will unlock cdata.p12 which can be used to communicate with the Tivos!
What was inside cdata.p12?
- Tivo Mind Sub CA issued by Tivo Inc. CA
- Tivo Inc. CA issued by Tivo Inc. CA
- 00000003976 issued by Tivo Mind Sub CA (marked for Client Authentication)
- Apple iPhone Device CA issued by Apple iPhone Device CA


Patching Application to retrieve the ssdata.p12 password


NOTE: Transcoder.didReceiveAuthenticationChallenge() uses ssdata and it has its own ssdata.p12 file with its own crypto info
; ISSUE: ssdata.p12 uses a passphrase determined by SUtils."dateTransform:other:"(""/MyW3}O aoG"","x$+2")
Arg1 is stored in 0x3C58E3, pointer to this data at 0x590238
Arg1 is stored in 0x3C58F0, pointer to this data at 0x590248
; Override these MOV commands to point to the pointers for our data arguments listed above
MOV             R3, (off_4B2078 - 0x27AE6) ; off_4B2078 ; Arg2
MOV             R2, (off_4B2074 - 0x27AE8) ; off_4B2074 ; Arg1
MOVW            R1, #0x8082
ADD             R3, PC ; off_4B2078 ; Arg2
ADD             R2, PC ; off_4B2074 ; Arg1
MOVT.W          R1, #0x50
MOV             R6, (classRef_SUtils - 0x27AFC) ; classRef_SUtils
LDR             R3, [R3] ; off_59E13C ; Arg2
LDR             R2, [R2] ; off_59E10C ; Arg1
ADD             R1, PC ; selRef_dateTransform_other_
ADD             R6, PC ; classRef_SUtils
STR             R0, [SP,#0x24+var_18]
LDR             R1, [R1] ; "dateTransform:other:"
LDR             R2, [R2] ; Arg1
LDR             R0, [R6] ; _OBJC_CLASS_$_SUtils
LDR             R3, [R3] ; Arg2
BLX             _objc_msgSend
MOVW            R1, #0x8062
Step1: Using existing CDATA changes...
Step2: Override data at cfstr_A5 (0x58CA08) reference to aA5 (0x3AE773) ["a$+5"] to aX2 (0x3C58F0) ["x$+2"]
Existing:
off_4B2078 = *off_59E13C = *cfstr_A5 = String Reference aA5 (length 4) @ 0x3AE773 = a$+5
New:
String Reference aX2 (length 4) @ 0x3C58F0
Change value @ 0x3AE773 from  61  24  2B  35  00
  to 78  24  2B  32  00
Step3: Override data at stru_58D948 (0x58C948) reference to aXUOC (0x3AE6D3) ["\x02\\|X\x02u\x1B\x06\x14O{c"] to aMyw3O (0x3C58E3) ["/MyW3}O aoG"]
Existing:
off_4B2074 = *off_59E10C = *stru_58D948 = String reference aXUOC (length 0xC) @ 0x3AE6D3 = "\x02\\|X\x02u\x1B\x06\x14O{c"
New:
String Reference aMyw3O (length 0xC) @ 0x3C58E3 = "/MyW3}O aoG"
Change value @ 0x3AE6D3 from  02  5C  7C  58  02  75  1B  06  14  4F  7B  63  00
  to 2F  4D  79  57  33  7D  4F  04  1A  61  6F  47  00  (2F4D7957337D4F041A616F4700)

Step 4: Run program, try to connect to Tivo on local network.  Once it fails, look through NSLog for our key
Output: "Client certification identity extraction error WiReKYd6bEDu"
WE HAVE OUR KEY!  This will unlock ssdata.p12 which can be used to communicate with the Tivos!
What was inside ssdata.p12?
- 00000004063 issued by Tivo Mind Sub CA (marked for Client Authentication)




Now that we have these passkeys, let's find some useful ways to use them...

TiVo App - cdata.p12 & ssdata.p12

After seeing these p12 files earlier, I was curious what they were for.

ssdata.p12


A quick search in IDA Pro turned up its usage location:



It appears that the method "connection:didReceiveAuthenticationChallenge" on the Transcoder object uses the "ssdata" string to call "extractClientCertInfo:withDateInfo:withSecIdentity:withClientCerts".

Looking at the iOS NSUrlConnectioNDelegate object documentation (https://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSURLConnectionDelegate_Protocol/Reference/Reference.html):

connection:didReceiveAuthenticationChallenge:

Sent when a connection must authenticate a challenge in order to download its request.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Parameters
connection
The connection sending the message.
challenge
The challenge that connection must authenticate in order to download its request.

This method must be getting called during an NSUrl request when the host is asking us to provide authentication, probably a client SSL certificate (ssdata).


Digging into the "extractClientCertInfo:withDateInfo:withSecIdentity:withClientCerts" code we see that it calls SecPKCS12Import.

; R0 = [Passphrase!] Arg 1 [r3] from extractClientCertInfo_withDateInfo_withSecIdentity_withClientCerts__ (withDateInfo is the fake argument name)
; R1 = File Contents of a P12 file
; R2 = [FileName] Arg 0 [r2] passed into SecurityUtils extractClientCertInfo_withDateInfo_withSecIdentity_withClientCerts__
; Stack 0x04 = Pointer to an array of items to be populated by this function

;CFArrayRef keyref = NULL;
; OSStatus sanityChesk = SecPKCS12Import((__bridge CFDataRef) p12Data, 
    ;                                     (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObject:password 
    ;                                                               forKey:(__bridge id)kSecImportExportPassphrase], 
    ;                                   &keyref);
; The complete p12 content will be in the keystore array.

It is clear that the "withDateInfo" argument is actually the .p12 file's password.  Looking back at what this argument is passed in as...

LDR             R4, [R2] ; _OBJC_CLASS_$_SecurityUtils
MOV             R2, (cfstr_Myw3O - 0x11A42C) ; "/MyW3}O aoG"
LDR             R0, [R3] ; _OBJC_CLASS_$_SUtils
MOVW            R3, #0x6E1A
LDR             R1, [R1] ; "dateTransform:other:"
MOVT.W          R3, #0x47
ADD             R2, PC  ; "/MyW3}O aoG"
ADD             R3, PC  ; "x$+2"
BLX             _objc_msgSend
MOV             R3, R0
It appears that SecurityUtils."dateTransform:other"() returns our password, it appears to take in the parameters "/MyW3}O aoG" & "x$+2".

Looking for other instances of dateTransform being called, I find TcpConnection."useSSL"() also calls it in the exact same fashion as Transcoder."connection:didReceiveAuthenticationChallenge"().  Except this time the parameters for dateTransform are "\x02\\|X\x02u\x1B\x06\x14O{c" & "a$+5" and it uses the "cdata" file.

This tells me that every TCPConnection object using SSL will automatically includes the SSL Client certificate CData.  This means that the IPScanner must be using this while scanning the network...

Looking at the disassembly for "dateTransform:other" we see that its code is obfuscated, making it nearly impossible to decypher.  This definitely isn't going to work...



TiVo App - Transcoder

I stumbled across a "Transcoder" object while viewing the disassembly...

TiVo App "Transcoder" object

This code is responsible for the log ouput :

Mar 30 20:46:24 XXXXXXXX-iPod Quicksilver[1286] <Warning>: config for http://10.0.0.109:49152/sysinfo/json/svcinfo:
Mar 30 20:46:24 XXXXXXXX-iPod Quicksilver[1286] <Warning>: config for http://10.0.0.109:49152/sysinfo/json/clients:
Mar 30 20:46:24 XXXXXXXX-iPod Quicksilver[1286] <Warning>: config for http://10.0.0.109:49152/sysinfo/json/main-dyn:

It was not hard to find given that IDA Pro can search for strings.

 I found the method "FetchJSONAtURL()" on the Transcoder object to be very interesting

Pseudo Code:

  1. Hits http://{TIVO_STREAM_IP}:49152/sysinfo/json/main-dyn
    1. Validate output, some callbacks
  2. Hits http://{TIVO_STREAM_IP}:49152/sysinfo/json/clients
    1. Validate output, some callbacks
  3. Hits http://{TIVO_STREAM_IP}:49152/sysinfo/json/svcinfo
    1. Validate output, some callbacks, saves the list of TSN's (TiVO Id's) listed
  4. If the TiVo stream has at least one TSN on the network (as dictated by our JSON response)
    1. Call "fetchWhiteBoxKey:withPort"
      1. Hits https:/{TIVO_STREAM_IP:49151/live-streaming/XX_DUMMY_TSN_XX/key.scbin?uuid={UNIQUE_IDENTIFIER}
      2. Save response as a property "whiteBoxKey" on object "Transcoder"

White-Box Key

The code pertaining to "key" and "WhiteBoxKey" has me interested, but also discouraged. White-Box cryptography is an industry term used to describe highly obfuscated encryption techniques that disguise the keys.  Things are not looking good.

Trying to hit the Transcoder URL's ourselves

Attempting to hit all the /sysinfo/json/* urls is no problem, the TiVo Stream spits out all the data we want.

However when we try to hit the "key.scbin" url we do not receive a response. something more must be going on here, are thye expecting a POST? or maybe an SSL client key (perhaps the one we found in the TiVo App folder earlier).

Let's start looking at the ssdata.p12 and cdata.p12 files we looked at earlier...

TiVo App - Network Manager

Once a TiVo box has been found with the IPScanner, the NetworkManager takes over.

TiVo Application "NetworkManager" object


The NetworkManager appears to be responsible for all remote procedure calls to the TiVo box.  This includes syncing its credentials, media access key, etc.  All communication with the TiVo box itself is done via an RPCBrowser, it sends out RPCRequests & gets back RPCResponses.

RpcRequestHandler Object

This object handles serializing/deserializing all communication between the TiVo box and the application.
Each request/response extends the object "TvTrioSchemBase", this is most likely an abstract class

Nothing promising yet...


For now, I do not care much about these objects as it does not directly involve the TiVo Stream and most likely is used to download guide data & view the boxes content (but not stream it).

TiVo App - IPScanner

Now that I have the log and the disassembly, I want to figure out how the iOS application finds TiVo within the local area network...

iOS Application TiVo Scanner

Looking at the disassembly, the object "NetworkManager" is responsible for telling the "IPScanner" object to scan the network for TiVo's (There is a "NetworkBrowser" object that uses the bonjour service to find TiVo's but I did not decide to pursue this).  


Scanning Process:
  1. The function "startBrowser" on the NetworkManager object creates an instance of "IPScanner"
  2. The method "beginScan" on the IPScanner object is called
    1. Grabs the wifi address (XXX.XXX.XXX.XXX), omits the last "XXX"
    2. Passes this "XXX.XXX.XXX" address to the "testAddresses" method
      1. Iterate through all subnets in this IP block...
        1. Create a TCP Connection for the IP on port 443
        2. At some point, the "TcpConnectionOpenedForInput" method on IPScanner will be called by the TCP Connection.
        3. If connection is successful, store this host & then attempt to connect on port 31339
        4. While on the 443 connection, retrieve the SSL certificate from the host.  Retrieve the certificate description and set this as the "TSN" for this host. This value will be in the format XXX-YYYY-ZZZZ-UUUU.  The app parses out the UUUU and attempts to match up this value with a set of known TiVo certificate descriptions via OBJC_CLASS_$_NetworkManager.HardwareTypeFromBodyId()
        5. If port 31339 is enabled, it gets a callback and the app sets this host to have its property "HasRemote()" equal to true
High level overview:

;  - Scan all hosts in the last subnet, look for port 443
;  - If we find port 443, look for port 31339 for the same host (if found, setRemote=true)
;  - Once we have scanned all, inspect the SSL security certificate for each host
;  - If SSL Certificate matches getHardwareByBodyId() and certain criteria, determine the HardwareType
;   - If HardwareType = 4, HasMind=true
; - If HardwareType = 5, then it is a transcoder (TiVo Stream) and put into successTranscoders, remove it from the SuccessTCDS
;  - At the end of the day, *m_SuccessTranscoders & *m_SuccessTCDS will be populated, transcoders will not be in successTCDS, and non-tivos will not be in either.



Now that we know how it scans, let's figure out what it does once it finds a TiVo...



Disassembly & Comments


/*  147 */
struct NetworkManager
{
  uint8_t NSObject_opaque[4];
  struct TiVo_Reachability *m_wanReach;
  struct TiVo_Reachability *m_lanReach;
  struct NSMutableArray *m_delegateList;
  struct NetworkBrowser *m_videosBrowser;
  struct NetworkBrowser *m_beaconBrowser;
  struct NetworkBrowser *m_rpcBrowser;
  struct NetworkBrowser *m_remoteBrowser;
  struct NetworkBrowser *m_xcodeBrowser;
  struct LocalTivoInfo *m_connectingTiVo;
  struct LocalTivoInfo *m_connectingTiVoMindRpc;
  struct LocalTivoInfo *m_manualBeacon;
  struct LocalTivoInfo *m_manualMindRpc;
  struct TivoInfo *m_tivo;
  struct NSMutableArray *m_tivoList;
  struct NSMutableArray *m_manualTivoList;
  struct NSMutableArray *m_xcodeList;
  int m_connectionMode;
  int m_wanModeAuthReqId;
  int m_s3ModeAuthReqId;
  int m_lanModeAuthReqId;
  int m_anonymousAuthReqId;
  int m_lineupSearchReqId;
  int m_tokenAuthReqId;
  int m_makAuthReqId;
  int m_bodyConfigReqId;
  struct TvLineup *m_guestModeLineup;
  struct NSString *m_s3ModeTsn;
  struct UIAlertView *m_reconnectAlert;
  struct IPScanner *m_ipScanner;
  struct AuthenticationToken *m_authToken;
  struct NSTimer *m_scanningTimer;
  int m_scanningAttempt;
  struct RpcRequestHandler *m_requestHandler;
  struct RpcRequestHandler *m_debugRequestHandler;
  struct RpcRequestHandler *m_tokenRequestHandler;
  struct TcpRemote *m_s3TcpRemote;
  struct NSDate *m_enteredForegroundDate;
  struct NSDate *m_enteredBackgroundDate;
  struct NSString *m_ipAddressWhenBackgrounded;
  struct NSTimer *m_reconnectTimer;
  struct NSTimer *m_networkActivityTimer;
  char m_shutdownBonjourAfterScan;
  struct NSString *m_wanHost;
  int m_wanPort;
  char enableBonjour;
  char enableIPScan;
  char enableServerDiscovery;
  char overrideSupportedPlatform;
  char enableRpcLogging;
  char enableRpcPerfLogging;
};

/*  152 */
struct IPScanner
{
  uint8_t NSObject_opaque[4];
  struct NSMutableArray *m_connections;
  struct NSMutableArray *m_successTCDs;
  struct NSMutableArray *m_successTranscoders;
};

NetworkManager."startBrowsing"() calls IP_Scanner



; IP SCANNER

; id __cdecl __IPScanner init_(struct IPScanner *self, SEL)
__IPScanner_init_

; Calling SP:
; -0x04 ? R8 is stored here
; -0x08 ? _OBJC_CLASS_$_IPScanner is stored here
; -0x12 ? This value is passed as the objc_super in objc_msgSendSuper2(struct objc_super *super, SEL op, ...)

var_18= -0x18
var_14= -0x14
var_10= -0x10

PUSH            {R4-R7,LR} ; Push registers R4-R7 & the link register onto the stack
ADD             R7, SP, #0xC ; Add 0xC (d12) to the stack pointer and store the value in R7  
; TODO - Why did we offset from the stack pointer?
STR.W           R8, [SP,#0xC+var_10]! ; Store R8 on the stack (address -0x04), auto-increment the SP to -0x04
; Add (0xc + -0x10) = -0x04 [d12 - d16 = -d4] to the stack pointer, then store the value of R8 there
; NOTE: This is the 1st argument (descending) on the stack!
SUB             SP, SP, #8 ; Subtract 8 from the stack pointer, the SP is now -0x12
MOV             R2, (classRef_IPScanner_0 - 0xD9C8E) ; classRef_IPScanner_0
MOV             R1, (selRef_init - 0xD9C90) ; selRef_init
ADD             R2, PC ; classRef_IPScanner_0
ADD             R1, PC ; selRef_init ("init")
STR             R0, [SP,#0x18+var_18] ; Store R0 on the stack (address -0x12).
; Add (0x18 + -0x18) = 0x00 [d24 - d24 = d0] to the stack pointer, then store the value of R0 there
; NOTE: This is the 4th argument (descending) on the stack!
LDR             R0, [R2] ; _OBJC_CLASS_$_IPScanner
LDR             R1, [R1] ; "init"
STR             R0, [SP,#0x18+var_14] ; Store R0 (_OBJC_CLASS_$_IPScanner) on the stack (address -0x08)
; Add (0x18 + -0x14) = 0x04 [d24 - d20 = d4] to the stack pointer, then store the address in R0
; NOTE: This is the 3rd argument (descending) on the stack!
; TOOD: This method does not appear to do anything since R0 is immediately overwritten?
MOV             R0, SP ; Copy the stack pointer (-0x12) to R0
BLX             _objc_msgSendSuper2 ; OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super [R0], SEL op, ... [R1])
; NOTE: This will return the memory pointer to the object we created
MOV             R4, R0 ; Copy R0 to R4
CMP             R4, #0 ; Compare R4 to 0
BEQ             loc_D9D24 ; If R4 is 0, branch (this is probably a null check)


; On success:
; Allocate a mutable array with initial capacity of 0, store the memory pointer to this mutable array in offset 0x04 of the IPScanner memory pointer (object property *m_connections)
; Allocate a mutable array with initial capacity of 2, store the memory pointer to this mutable array in offset 0x08 of the IPScanner memory pointer (object property *m_successTCDs)
; Allocate a mutable array with initial capacity of 1, store the memory pointer to this mutable array in offset 0x0C of the IPScanner memory pointer (object property *m_successTranscoders)






; IPScanner - (void)beginScan
; Attributes: bp-based frame

; void __cdecl __IPScanner beginScan_(struct IPScanner *self, SEL)
__IPScanner_beginScan_

PUSH            {R4-R7,LR} ;
ADD             R7, SP, #0xC ;
PUSH.W          {R8,R10,R11} ;
SUB             SP, SP, #8 ;
MOV             R11, R0 ;
; Clear all instances from *m_successTCDS
MOV             R0, (_OBJC_IVAR_$_IPScanner.m_successTCDs - 0xD9EC8) ; NSMutableArray *m_successTCDs;
MOVW            R1, #0x4E1E ;
ADD             R0, PC  ; NSMutableArray *m_successTCDs; ;
MOVT.W          R1, #0x45 ;
ADD             R1, PC ; selRef_removeAllObjects ;
LDR             R0, [R0] ; NSMutableArray *m_successTCDs; ;
LDR             R5, [R1] ; "removeAllObjects" ;
LDR.W           R0, [R11,R0] ;
MOV             R1, R5 ;
BLX             _objc_msgSend ;
; Clear all instances from *m_successTranscoders
MOVW            R0, #0xB330 ;
MOV             R1, R5
MOVT.W          R0, #0x4A
ADD             R0, PC  ; NSMutableArray *m_successTranscoders;
LDR             R0, [R0] ; NSMutableArray *m_successTranscoders;
LDR.W           R0, [R11,R0]
BLX             _objc_msgSend
; Clear all instances from *m_connections
MOVW            R0, #0xB312
MOV             R1, R5
MOVT.W          R0, #0x4A
ADD             R0, PC  ; NSMutableArray *m_connections;
LDR             R0, [R0] ; NSMutableArray *m_connections;
LDR.W           R0, [R11,R0]
BLX             _objc_msgSend
; Retrieve the "wifiIPAddress" and set its value to R5
MOV             R0, (selRef_wifiIPAddress - 0xD9F12) ; selRef_wifiIPAddress
ADD             R0, PC ; selRef_wifiIPAddress
LDR             R1, [R0] ; "wifiIPAddress"
MOV             R0, R11
BLX             _objc_msgSend
MOV             R5, R0
; Logging, pass our "wifiIPAddress" along
MOV             R0, (cfstr_IpScannerStart - 0xD9F28) ; "IP SCANNER: Starting IP Address:%@"
MOV             R1, R5
ADD             R0, PC  ; "IP SCANNER: Starting IP Address:%@"
BLX             _NSLog
; Split the "wifiIPAddress" value by ".", store the result in R6
MOV             R0, (selRef_componentsSeparatedByString_ - 0xD9F3A) ; selRef_componentsSeparatedByString_
MOVW            R2, #0xE0B8
ADD             R0, PC ; selRef_componentsSeparatedByString_
MOVT.W          R2, #0x4A
ADD             R2, PC  ; "."
LDR             R1, [R0] ; "componentsSeparatedByString:"
MOV             R0, R5
BLX             _objc_msgSend
MOV             R6, R0
; Retrieve the count of the split, If it does not have 4 parts, branch
MOV             R0, (selRef_count - 0xD9F54) ; selRef_count
ADD             R0, PC ; selRef_count
LDR             R1, [R0] ; "count"
MOV             R0, R6
BLX             _objc_msgSend
CMP             R0, #4
BNE             loc_D9FC0

; if(String.Split(WifiIPAddress,".") == 4){
WifiIPAddress_in_X.X.X._format
; Retrieve value at index [0] in the array created by the String.Split(), store it in R10
MOVW            R1, #0xAE54
MOVS            R2, #0
MOVT.W          R1, #0x45
MOV             R0, (selRef_objectAtIndex_ - 0xD9F76) ; selRef_objectAtIndex_
ADD             R1, PC ; classRef_NSString
ADD             R0, PC ; selRef_objectAtIndex_
LDR.W           R8, [R1] ; _OBJC_CLASS_$_NSString
LDR             R5, [R0] ; "objectAtIndex:"
MOV             R0, R6
MOV             R1, R5
BLX             _objc_msgSend
MOV             R10, R0
; Retrieve value at index [1] in the array created by the String.Split(), store it in R4
MOV             R0, R6
MOV             R1, R5
MOVS            R2, #1
BLX             _objc_msgSend
MOV             R4, R0
; Retrieve value at index [2] in the array created by the String.Split(), store it in R5
MOV             R0, R6
MOV             R1, R5
MOVS            R2, #2
BLX             _objc_msgSend
MOV             R5, R0
; Call NSString.StringWithFormat("%@.%@.%@.",), store its value in R5
MOV             R0, (selRef_stringWithFormat_ - 0xD9FAC) ; selRef_stringWithFormat_
MOVW            R2, #0x47D6
ADD             R0, PC ; selRef_stringWithFormat_
MOVT.W          R2, #0x4B
ADD             R2, PC  ; "%@.%@.%@."
MOV             R3, R10 ; First parameter of StringWithFormat is R3, which is our index [0]
LDR             R1, [R0] ; "stringWithFormat:"
MOV             R0, R8
STRD.W          R4, R5, [SP] ; Push R4 (index [1]) & R5 (index [2]) onto the stack
BLX             _objc_msgSend
MOV             R5, R0
; }
; At this point, our base IP address is stored in R5, assumed to be in the "X.X.X" format
loc_D9FC0
; Log our base ip address
MOVW            R0, #0x47CA
MOV             R1, R5
MOVT.W          R0, #0x4B
ADD             R0, PC  ; "IP SCANNER: Base IP Address:%@"
BLX             _NSLog
; Execute  IPScanner.testAddresses("X.X.X")
MOVW            R0, #0x7EE6
MOV             R2, R5
MOVT.W          R0, #0x45
ADD             R0, PC ; selRef_testAddresses_
LDR             R1, [R0] ; "testAddresses:"
MOV             R0, R11
ADD             SP, SP, #8
POP.W           {R8,R10,R11}
POP.W           {R4-R7,LR}
B.W             j__objc_msgSend
; End of function -[IPScanner beginScan]



; IPScanner - (void)testAddresses:(id)
; Attributes: bp-based frame

; void __cdecl __IPScanner testAddresses__(struct IPScanner *self, SEL, id)
__IPScanner_testAddresses__


; Local Variables:
__text:000D9DA8 Counter         = -0x3C
__text:000D9DA8 var_38          = -0x38
__text:000D9DA8 RefInit         = -0x34
__text:000D9DA8 RefSetDelegate  = -0x30
__text:000D9DA8 RefOpenWithHost = -0x2C
__text:000D9DA8 RefConnections  = -0x28
__text:000D9DA8 RefAddObject    = -0x24
__text:000D9DA8 RefRelease      = -0x20


; Parameter (id)
__text:000D9DA8 Id              = -0x1C ; PARAMETER


; Initialize references to things before we loop ...
__text:000D9DA8                 PUSH            {R4-R7,LR}
__text:000D9DAA                 ADD             R7, SP, #0xC
__text:000D9DAC                 PUSH.W          {R8,R10,R11}
__text:000D9DB0                 SUB             SP, SP, #0x24
__text:000D9DB2                 STR             R2, [SP,#0x3C+Id] ; Save our "id" in R2 to the stack
__text:000D9DB4                 MOV             R2, (_OBJC_IVAR_$_IPScanner.m_connections - 0xD9DCE) ; NSMutableArray *m_connections;
__text:000D9DBC                 MOV             R3, (selRef_addObject_ - 0xD9DE8) ; selRef_addObject_
__text:000D9DC4                 MOV             R5, R0
__text:000D9DC6                 MOVW            R0, #0x4F30
__text:000D9DCA                 ADD             R2, PC  ; NSMutableArray *m_connections;
__text:000D9DCC                 MOVT.W          R0, #0x45
__text:000D9DD0                 MOV             R4, (selRef_release - 0xD9DEA) ; selRef_release
__text:000D9DD8                 MOV             R1, (selRef_init - 0xD9DF2) ; selRef_init
__text:000D9DE0                 MOVW            R6, #0x5C64
__text:000D9DE4                 ADD             R3, PC ; selRef_addObject_
__text:000D9DE6                 ADD             R4, PC ; selRef_release
__text:000D9DE8                 MOVT.W          R6, #0x45
__text:000D9DEC                 ADD             R0, PC ; selRef_setDelegate_
__text:000D9DEE                 ADD             R1, PC ; selRef_init
__text:000D9DF0                 ADD             R6, PC ; selRef_openWithHost_port_SSLDesc_errorStr_
__text:000D9DF2                 LDR             R4, [R4] ; "release"
__text:000D9DF4                 MOV.W           R8, #0  ; Initialize counter loop to 0
__text:000D9DF8                 LDR             R3, [R3] ; "addObject:"
__text:000D9DFA                 LDR             R2, [R2] ; NSMutableArray *m_connections;
__text:000D9DFC                 STR             R3, [SP,#0x3C+RefAddObject]
__text:000D9DFE                 STR             R4, [SP,#0x3C+RefRelease]
__text:000D9E00                 STR             R2, [SP,#0x3C+RefConnections]
__text:000D9E02                 MOVW            R2, #0x4DB6
__text:000D9E06                 LDR             R3, [R6] ; "openWithHost:port:SSLDesc:errorStr:"
__text:000D9E08                 MOVT.W          R2, #0x45
__text:000D9E0C                 LDR             R0, [R0] ; "setDelegate:"
__text:000D9E0E                 ADD             R2, PC ; selRef_alloc
__text:000D9E10                 LDR             R1, [R1] ; "init"
__text:000D9E12                 STR             R3, [SP,#0x3C+RefOpenWithHost]
__text:000D9E14                 STR             R0, [SP,#0x3C+RefSetDelegate]
__text:000D9E16                 STR             R1, [SP,#0x3C+RefInit]
__text:000D9E18                 MOV             R0, (selRef_stringWithFormat_ - 0xD9E28) ; selRef_stringWithFormat_
__text:000D9E20                 LDR.W           R10, [R2] ; "alloc"
__text:000D9E24                 ADD             R0, PC ; selRef_stringWithFormat_
__text:000D9E26                 LDR.W          


; This loop will iterate over X.X.X.0 through X.X.X.255
IP_Iterations_Loop
; Create our X.X.X.i string using our counter value, save the value into R6
MOVW            R0, #0xAF8C
MOV             R1, R11
MOVT.W          R0, #0x45
STR.W           R8, [SP,#0x3C+refTopOfStack] ; Update the stack counter with the real counters value, this will also be the 3rd argument for the string formatter
ADD             R0, PC ; classRef_NSString
LDR             R0, [R0] ; _OBJC_CLASS_$_NSString
MOV             R2, (cfstr_D_4 - 0xD9E4A) ; "%@%d"
LDR             R3, [SP,#0x3C+Id]
ADD             R2, PC  ; "%@%d"
BLX             _objc_msgSend
MOV             R6, R0
; Create a TCPConnection object
MOV             R0, (classRef_TcpConnection - 0xD9E5C) ; classRef_TcpConnection
MOV             R1, R10
ADD             R0, PC ; classRef_TcpConnection
LDR             R0, [R0] ; _OBJC_CLASS_$_TcpConnection
BLX             _objc_msgSend
; Call TCPConnection.Init();
LDR             R1, [SP,#0x3C+RefInit]
BLX             _objc_msgSend
; Call TCPConnection.SetDelegate(*IPScanner);
; NOTE: This appears to be a register that allows IPScanner to handle callbacks from TCPConnection
LDR             R1, [SP,#0x3C+RefSetDelegate]
MOV             R2, R5 ; Set R5 equal to our current IPScanner object
MOV             R4, R0 ; Set R4 equal to our TCPConnection object (this method must return a different object and override R0)
BLX             _objc_msgSend
; Execute TCPConnection("OpenWithHost:port:SSLDesc:errorStr")( "X.X.X.i", "443" )
MOVS            R1, #3
MOVS            R0, #0
STR             R1, [SP,#0x3C+refTopOfStack] ; set SSLDesc
MOV             R2, R6 ; Put our calculated IP into R6, (Parameter 1)
STR             R0, [SP,#0x3C+var_38] ; set ErrorMessage
MOV             R0, R4 ; Set R0 = TCPConnection Object
LDR             R1, [SP,#0x3C+RefOpenWithHost] ; Load "openWithHost:port:SSLDesc:errorStr:" into R1
MOVW            R3, #0x1BB ; Passed in a parameter of "443"
BLX             _objc_msgSend
; *m_connections.AddObject(TCPConnection) - Add our TCPConnection to the m_connections array
LDR             R0, [SP,#0x3C+RefConnections]
MOV             R2, R4
LDR             R1, [SP,#0x3C+RefAddObject]
LDR             R0, [R5,R0]
BLX             _objc_msgSend
; TCPConnection.Release()
LDR             R1, [SP,#0x3C+RefRelease]
MOV             R0, R4
BLX             _objc_msgSend
; Increment the counter
ADD.W           R8, R8, #1
CMP.W           R8, #0x100 ; 256 (Full IP Range)
; If(counter != 256){
BNE             IP_Iterations_Loop

; }
; Cleanup
ADD             SP, SP, #0x24
POP.W           {R8,R10,R11}
POP             {R4-R7,PC}
; End of function -[IPScanner testAddresses:]


IPScanner - (void)TcpConnectionOpenedForInput:(id)
Attributes: bp-based frame
; void __cdecl __IPScanner TcpConnectionOpenedForInput__(struct IPScanner *self, SEL, TcpConnectionId)
__IPScanner_TcpConnectionOpenedForInput__

; Local variables
__text:000DA2F8 var_48          = -0x48
__text:000DA2F8 var_44          = -0x44
__text:000DA2F8 var_40          = -0x40
__text:000DA2F8 valPort         = -0x3C
__text:000DA2F8 refObjectAtIndex= -0x38
__text:000DA2F8 refAddress      = -0x34
__text:000DA2F8 refIsEqualTostring= -0x30
__text:000DA2F8 refSetHasRemote = -0x2C
__text:000DA2F8 var_28          = -0x28
__text:000DA2F8 refCountOrAlloc        = -0x24
__text:000DA2F8 refHost         = -0x20
; Parameters
__text:000DA2F8 TcpConnectionId              = -0x1C
; Typical Init shit, put TcpConnectionId into R6, put self reference into R8
__text:000DA2F8                 PUSH            {R4-R7,LR}
__text:000DA2FA                 ADD             R7, SP, #0xC
__text:000DA2FC                 PUSH.W          {R8,R10,R11}
__text:000DA300                 SUB             SP, SP, #0x30
__text:000DA302                 MOV             R6, R2
__text:000DA304                 MOV             R8, R0
__text:000DA306                 STR             R6, [SP,#0x48+TcpConnectionId]
; Retrieve the host from the TCPConnection, put it into R5
__text:000DA308                 MOV             R0, (selRef_host - 0xDA314) ; selRef_host
__text:000DA310                 ADD             R0, PC ; selRef_host
__text:000DA312                 LDR             R1, [R0] ; "host"
__text:000DA314                 MOV             R0, R6
__text:000DA316                 STR             R1, [SP,#0x48+refHost]
__text:000DA318                 BLX             _objc_msgSend
__text:000DA31C                 MOV             R5, R0
; Retrieve the port from the TCPConnection, put it into R2
__text:000DA31E                 MOV             R0, (selRef_port - 0xDA32A) ; selRef_port
__text:000DA326                 ADD             R0, PC ; selRef_port
__text:000DA328                 LDR             R4, [R0] ; "port"
__text:000DA32A                 MOV             R0, R6
__text:000DA32C                 MOV             R1, R4
__text:000DA32E                 BLX             _objc_msgSend
__text:000DA332                 MOV             R2, R0
; Log some shit
__text:000DA334                 MOV             R0, (cfstr_Tcpconnectio_3 - 0xDA342) ; "TcpConnectionOpenedForInput ip:%@ port:%d"
__text:000DA33C                 MOV             R1, R5
__text:000DA33E                 ADD             R0, PC  ; "TcpConnectionOpenedForInput ip:%@ port:%d"
__text:000DA340                 MOV             R5, R8
__text:000DA342                 BLX             _NSLog
; Retrieve the port from the TCPConnection (again), put it into R10
__text:000DA346                 MOV             R0, R6
__text:000DA348                 MOV             R1, R4
__text:000DA34A                 BLX             _objc_msgSend
__text:000DA34E                 MOV             R10, R0
; Retrieve the port from the TCPConnection (again), store the value of the port into a local variable, 
__text:000DA350                 MOV             R0, R6
__text:000DA352                 MOV             R1, R4
__text:000DA354                 STR.W           R10, [SP,#0x48+valPort]
__text:000DA358                 BLX             _objc_msgSend
__text:000DA35C                 MOVW            R1, #0xAEA6
; Store the port in R4, retrieve the count of *m_successTCDs array
__text:000DA35C                 MOVW            R1, #0xAEA6
__text:000DA360                 MOV             R4, R0
__text:000DA362                 MOVT.W          R1, #0x4A
__text:000DA366                 MOVW            R0, #0x48E0
__text:000DA36A                 ADD             R1, PC  ; NSMutableArray *m_successTCDs;
__text:000DA36C                 MOVT.W          R0, #0x45
__text:000DA370                 ADD             R0, PC ; selRef_count
__text:000DA372                 LDR.W           R8, [R1] ; NSMutableArray *m_successTCDs;
__text:000DA376                 LDR             R1, [R0] ; "count"
__text:000DA378                 STR             R1, [SP,#0x48+refCountOrAlloc]
__text:000DA37A                 LDR.W           R0, [R5,R8]
__text:000DA37E                 BLX             _objc_msgSend
; If port is = 443, set R1 equal to 1 (this is a flag)
__text:000DA382                 MOVW            R1, #0x1BB
__text:000DA386                 CMP             R10, R1
__text:000DA388                 MOV.W           R1, #0
__text:000DA38C                 MOV.W           R2, #0
__text:000DA390                 IT EQ
__text:000DA392                 MOVEQ           R1, #1
; Store our flag, if the port is 0x7A6B (31339) set R2 equal to 1 (this is a flag)
__text:000DA394                 MOVW            R3, #0x7A6B
__text:000DA398                 STR             R1, [SP,#0x48+FlagIsSSL]
__text:000DA39A                 CMP             R4, R3
__text:000DA39C                 IT EQ
__text:000DA39E                 MOVEQ           R2, #1
; Store our flag, check if the count of *m_successTCDS is 0, branch if it is zero
__text:000DA3A0                 CMP             R0, #0
__text:000DA3A2                 STR             R2, [SP,#0x48+FlagIsPort31339]
__text:000DA3A4                 BEQ
; if(count(*m_successTCDs) != 0){
; CODE IGNORED - Simply checks if the array already has this HOST, if it does, updates the HasRemote() using the 31339 port flag (if 31339 port, HasRemote=true)
; }
loc_DA43C
; Allocate space for an IPScannerInfo object
; Store "alloc" into "refCountOrAlloc"
MOV             R1, (selRef_alloc - 0xDA450) ; selRef_alloc
MOV             R0, (classRef_IPScannerInfo - 0xDA452) ; classRef_IPScannerInfo
ADD             R1, PC ; selRef_alloc
ADD             R0, PC ; classRef_IPScannerInfo
LDR             R1, [R1] ; "alloc"
LDR             R0, [R0] ; _OBJC_CLASS_$_IPScannerInfo
STR             R1, [SP,#0x48+refCountOrAlloc]
BLX             _objc_msgSend
; Intialize the IPScannerInfo object
MOV             R1, (selRef_init - 0xDA466) ; selRef_init
ADD             R1, PC ; selRef_init
LDR.W           R10, [R1] ; "init"
MOV             R1, R10
BLX             _objc_msgSend
; Save IPScannerInfo in R4, retrieve the Host from TCPConnection & store it in R2
LDR             R6, [SP,#0x48+TcpConnectionId]
MOV             R4, R0
LDR             R1, [SP,#0x48+refHost]
MOV             R0, R6
BLX             _objc_msgSend
MOV             R2, R0
; Call IPScannerInfo."setAddress:"(TCPConnection."Host")
MOV             R0, (selRef_setAddress_ - 0xDA488) ; selRef_setAddress_
ADD             R0, PC ; selRef_setAddress_
LDR             R1, [R0] ; "setAddress:"
MOV             R0, R4
BLX             _objc_msgSend
; Call IPScannerInfo."setHasVideos:"(FlagIsSSL)
MOV             R0, (selRef_setHasVideos_ - 0xDA49C) ; selRef_setHasVideos_
LDR             R2, [SP,#0x48+FlagIsSSL]
ADD             R0, PC ; selRef_setHasVideos_
LDR             R1, [R0] ; "setHasVideos:"
MOV             R0, R4
BLX             _objc_msgSend
; Call IPScannerInfo."setLastConnection:"(*TCPConnection)
MOVW            R0, #0x7A20
MOV             R2, R6
MOVT.W          R0, #0x45
ADD             R0, PC ; selRef_setLastConnection_
LDR             R1, [R0] ; "setLastConnection:"
MOV             R0, R4
BLX             _objc_msgSend
; Check if port is 443, branch if it is not
LDR             R0, [SP,#0x48+valPort]
MOVW            R1, #0x1BB
CMP             R0, R1
BNE             loc_DA560
; if(Port == 443){
; Call *m_successTCDs."addObject:"(*IPScannerInfo)
MOVW            R0, #0x4716
MOV             R2, R4
MOVT.W          R0, #0x45
ADD             R0, PC ; selRef_addObject_
LDR.W           R11, [R0] ; "addObject:"
LDR.W           R0, [R5,R8]
MOV             R1, R11
BLX             _objc_msgSend
; Create a TcpConnection object - Call _OBJC_CLASS_$_TcpConnection."alloc"()
MOV             R0, (classRef_TcpConnection - 0xDA4E8) ; classRef_TcpConnection
LDR             R1, [SP,#0x48+refCountOrAlloc]
ADD             R0, PC ; classRef_TcpConnection
LDR             R0, [R0] ; _OBJC_CLASS_$_TcpConnection
BLX             _objc_msgSend
; Call TcpConnection."init"(), save the TcpConnection instance in R6
MOV             R1, R10
BLX             _objc_msgSend
MOV             R6, R0
; Set the delegate of the TcpConnection to be the IPScanner (self)
MOV             R0, (selRef_setDelegate_ - 0xDA502) ; selRef_setDelegate_
MOV             R2, R5
ADD             R0, PC ; selRef_setDelegate_
LDR             R1, [R0] ; "setDelegate:"
MOV             R0, R6
BLX             _objc_msgSend
; Get IPScannerInfo."address:" and store it in R2
MOV             R0, (selRef_address - 0xDA514) ; selRef_address
ADD             R0, PC ; selRef_address
LDR             R1, [R0] ; "address"
MOV             R0, R4
BLX             _objc_msgSend
MOV             R2, R0
; Since we found a port 443 match, try to find a port 31339 match.
; Call TcpConnection."selRef_openWithHost_port_SSLDesc_errorStr_"(IPScannerInfo.Address, 31339)
MOV             R0, (selRef_openWithHost_port_SSLDesc_errorStr_ - 0xDA52C) ; selRef_openWithHost_port_SSLDesc_errorStr_
MOVW            R3, #0x7A6B ; 31339
ADD             R0, PC ; selRef_openWithHost_port_SSLDesc_errorStr_
LDR             R1, [R0] ; "openWithHost:port:SSLDesc:errorStr:"
MOVS            R0, #0
STR             R0, [SP,#0x48+var_48]
STR             R0, [SP,#0x48+var_44]
MOV             R0, R6
BLX             _objc_msgSend
; Add the new connection to our connections list
; Call *m_connections."addObject:"(*TcpConnection)
MOVW            R0, #0xACC8
MOV             R1, R11
MOVT.W          R0, #0x4A
MOV             R2, R6
ADD             R0, PC  ; NSMutableArray *m_connections;
LDR             R0, [R0] ; NSMutableArray *m_connections;
LDR             R0, [R5,R0]
BLX             _objc_msgSend
; Call TcpConnection.Release()
MOV             R0, (selRef_release - 0xDA55A) ; selRef_release
ADD             R0, PC ; selRef_release
LDR             R1, [R0] ; "release"
MOV             R0, R6
BLX             _objc_msgSend
; }
; Release IPScannerInfo object, normal cleanup
loc_DA560
MOV             R0, (selRef_release - 0xDA56C) ; selRef_release
ADD             R0, PC ; selRef_release
LDR             R1, [R0] ; "release"
MOV             R0, R4
ADD             SP, SP, #0x30
POP.W           {R8,R10,R11}
POP.W           {R4-R7,LR}
B.W             j__objc_msgSend






; NOTE: NetworkManager.ScanningTimeout() calls IPScanner.EndScan()



; The jist of this function is that it will take *m_successTCDs items and determine if they have minds, are transcoders, etc based on their TSN (SSL Cert Description)
; If an item in *m_successTCDS is a transcoder, it will be removed from the array and added to *m_successTranscoders/
; No matter what happens, at the end all objects will be removed from *m_connections;

IPScanner - (void)endScan
Attributes: bp-based frame

void __cdecl __IPScanner endScan_(struct IPScanner *self, SEL)
__IPScanner_endScan_                    ; DATA XREF: __objc_const:004DD6F8 o

; Internal attributes
__text:000D9FF0 refSelf         = -0x78
__text:000D9FF0 valTSNPart2          = -0x74
__text:000D9FF0 valTSNPart3          = -0x70
__text:000D9FF0 strLastConnection= -0x6C
__text:000D9FF0 strServerCertDescription= -0x68
__text:000D9FF0 strComponentsSeparatedByString= -0x64
__text:000D9FF0 strStringWithFormat= -0x60
__text:000D9FF0 strHardwareTypeFromBodyID= -0x5C
__text:000D9FF0 refSuccessTranscoders= -0x58
__text:000D9FF0 strAddObject    = -0x54
__text:000D9FF0 strRemoveObject = -0x50
__text:000D9FF0 strSetLastConnection= -0x4C
__text:000D9FF0 strDump         = -0x48
__text:000D9FF0 strSetHasMind   = -0x44
__text:000D9FF0 strAddress      = -0x40
__text:000D9FF0 refSuccessTCDs  = -0x3C
__text:000D9FF0 StrCount        = -0x38
__text:000D9FF0 refNSStringInstance= -0x34
__text:000D9FF0 refNSStringClass= -0x30
__text:000D9FF0 valIncrementerCount= -0x2C
__text:000D9FF0 strObjectAtIndex= -0x28
__text:000D9FF0 strSetTSN       = -0x24
__text:000D9FF0 strLength       = -0x20
; First Parameter ?
; NOTE: This doesn't make much sense
__text:000D9FF0 strTSN          = -0x1C

; Typical init, store self reference in R10

__text:000D9FF0                 PUSH            {R4-R7,LR}
__text:000D9FF2                 ADD             R7, SP, #0xC
__text:000D9FF4                 PUSH.W          {R8,R10,R11}
__text:000D9FF8                 SUB             SP, SP, #0x60
__text:000D9FFA                 MOV             R10, R0
; Log
__text:000D9FFC                 MOV             R0, (cfstr_IpScannerEndSc - 0xDA008) ; "IP SCANNER: end scan"
__text:000DA004                 ADD             R0, PC  ; "IP SCANNER: end scan"
__text:000DA006                 BLX             _NSLog
; Get the count of items in *m_successTCDS
__text:000DA00A                 MOV             R1, (_OBJC_IVAR_$_IPScanner.m_successTCDs - 0xDA01A) ; NSMutableArray *m_successTCDs;
__text:000DA012                 MOVW            R0, #0x4C34
__text:000DA016                 ADD             R1, PC  ; NSMutableArray *m_successTCDs;
__text:000DA018                 MOVT.W          R0, #0x45
__text:000DA01C                 ADD             R0, PC ; selRef_count
__text:000DA01E                 LDR             R6, [R1] ; NSMutableArray *m_successTCDs;
__text:000DA020                 LDR             R1, [R0] ; "count"
__text:000DA022                 STR             R6, [SP,#0x78+refSuccessTCDs]
__text:000DA024                 LDR.W           R0, [R10,R6]
__text:000DA028                 STR             R1, [SP,#0x78+strCount]
__text:000DA02A                 BLX             _objc_msgSend
; Store the count-1 (last index) of *m_successTCDS in R2 (this will be used as the incrementer later)
; If there is not at least 1 item in *m_successTCDS, branch
__text:000DA02E                 SUBS            R2, R0, #1
__text:000DA030                 CMP             R2, #0
__text:000DA032                 BLT.W           loc_DA2AA

; if(count(*m_successTCDS) > 0){
; Initialize properties before looping
; init_before_loop
MOV             R3, (selRef_dump - 0xDA062) ; selRef_dump
MOV             R0, (selRef_setHasMind_ - 0xDA05A) ; selRef_setHasMind_
MOV             R9, (selRef_removeObject_ - 0xDA07A) ; selRef_removeObject_
MOV             R5, (selRef_address - 0xDA060) ; selRef_address
ADD             R0, PC ; selRef_setHasMind_
MOVW            R1, #0x4B6E
ADD             R5, PC ; selRef_address
ADD             R3, PC ; selRef_dump
MOVT.W          R1, #0x45
MOVW            R4, #0x7E52
LDR             R0, [R0] ; "setHasMind:"
MOVT.W          R4, #0x45
LDR             R5, [R5] ; "address"
STR             R0, [SP,#0x78+strSetHasMind]
ADD             R1, PC ; selRef_addObject_
LDR             R0, [R3] ; "dump"
ADD             R9, PC ; selRef_removeObject_
STR             R5, [SP,#0x78+strAddress]
ADD             R4, PC ; selRef_setLastConnection_
STR             R0, [SP,#0x78+strDump]
MOV             R0, (_OBJC_IVAR_$_IPScanner.m_successTranscoders - 0xDA08C) ; NSMutableArray *m_successTranscoders;
LDR             R3, [R4] ; "setLastConnection:"
ADD             R0, PC  ; NSMutableArray *m_successTranscoders;
LDR.W           R11, [R9] ; "removeObject:"
LDR             R1, [R1] ; "addObject:"
LDR             R0, [R0] ; NSMutableArray *m_successTranscoders;
STR             R3, [SP,#0x78+strSetLastConnection]
STR.W           R11, [SP,#0x78+strRemoveObject]
STR             R1, [SP,#0x78+strAddObject]
STR             R0, [SP,#0x78+refSuccessTranscoders]
MOV             R0, (selRef_hardwareTypeFromBodyID_tsnIndex_ - 0xDA0A8) ; selRef_hardwareTypeFromBodyID_tsnIndex_
ADD             R0, PC ; selRef_hardwareTypeFromBodyID_tsnIndex_
LDR             R0, [R0] ; "hardwareTypeFromBodyID:tsnIndex:"
STR             R0, [SP,#0x78+strHardwareTypeFromBodyID]
MOV             R0, (selRef_stringWithFormat_ - 0xDA0B6) ; selRef_stringWithFormat_
ADD             R0, PC ; selRef_stringWithFormat_
LDR             R0, [R0] ; "stringWithFormat:"
STR             R0, [SP,#0x78+strStringWithFormat]
MOV             R0, (selRef_length - 0xDA0C4) ; selRef_length
ADD             R0, PC ; selRef_length
LDR             R0, [R0] ; "length"
STR             R0, [SP,#0x78+strLength]
MOV             R0, (selRef_componentsSeparatedByString_ - 0xDA0D2) ; selRef_componentsSeparatedByString_
ADD             R0, PC ; selRef_componentsSeparatedByString_
LDR             R0, [R0] ; "componentsSeparatedByString:"
STR             R0, [SP,#0x78+strComponentsSeparatedByString]
MOV             R0, (selRef_tsn - 0xDA0E0) ; selRef_tsn
ADD             R0, PC ; selRef_tsn
LDR             R0, [R0] ; "tsn"
STR             R0, [SP,#0x78+strTSN]
MOV             R0, (selRef_setTsn_ - 0xDA0EE) ; selRef_setTsn_
ADD             R0, PC ; selRef_setTsn_
LDR             R0, [R0] ; "setTsn:"
STR             R0, [SP,#0x78+strSetTSN]
MOV             R0, (selRef_serverCertDescription - 0xDA0FC) ; selRef_serverCertDescription
ADD             R0, PC ; selRef_serverCertDescription
LDR             R0, [R0] ; "serverCertDescription"
STR             R0, [SP,#0x78+strServerCertDescription]
MOV             R0, (selRef_lastConnection - 0xDA10A) ; selRef_lastConnection
ADD             R0, PC ; selRef_lastConnection
LDR             R0, [R0] ; "lastConnection"
STR             R0, [SP,#0x78+strLastConnection]
MOV             R0, (selRef_objectAtIndex_ - 0xDA118) ; selRef_objectAtIndex_
ADD             R0, PC ; selRef_objectAtIndex_
LDR             R0, [R0] ; "objectAtIndex:"
STR             R0, [SP,#0x78+strObjectAtIndex]
; TODO
beginning_of_loop
; Call m_successTCDs."ObjectAtIndex"(i), updated incrementer in memory
__text:000DA11A                 LDR.W           R0, [R10,R6]
__text:000DA11E                 LDR             R1, [SP,#0x78+strObjectAtIndex]
__text:000DA120                 STR             R2, [SP,#0x78+valIncrementerCount]
__text:000DA122                 BLX             _objc_msgSend
; Set the retrieved *IPScannerInfo reference into R5
; Retrieve the last *TCPConnection from the *IPScannerInfo
__text:000DA126                 LDR             R1, [SP,#0x78+strLastConnection]
__text:000DA128                 MOV             R5, R0
__text:000DA12A                 BLX             _objc_msgSend
; Call *TCPConnection."ServerCertDescription"(), store value in R2
__text:000DA12E                 LDR             R1, [SP,#0x78+strServerCertDescription]
__text:000DA130                 BLX             _objc_msgSend
; Call *IPScannerInfo."SetTSN"(*ServerCertDescription)
; TODO - This is very important
__text:000DA134                 LDR             R1, [SP,#0x78+strSetTSN]
__text:000DA136                 MOV             R2, R0
__text:000DA138                 MOV             R0, R5
__text:000DA13A                 BLX             _objc_msgSend
; Call *IPScannerInfo."TSN"(), store the result in R1
__text:000DA13E                 LDR             R4, [SP,#0x78+strTSN]
__text:000DA140                 MOV             R0, R5
__text:000DA142                 MOV             R1, R4
__text:000DA144                 BLX             _objc_msgSend
__text:000DA148                 MOV             R1, R0
; Log
__text:000DA14A                 MOV             R0, (cfstr_IpscannerCertD - 0xDA156) ; "IPScanner:Cert Description:%@"
__text:000DA152                 ADD             R0, PC  ; "IPScanner:Cert Description:%@"
__text:000DA154                 BLX             _NSLog
; Call *IPScannerInfo."TSN"()
__text:000DA158                 MOV             R0, R5
__text:000DA15A                 MOV             R1, R4
__text:000DA15C                 BLX             _objc_msgSend
; Call *TSN."ComponentsSeparatedByString"("-"), store result in R8
__text:000DA160                 LDR             R1, [SP,#0x78+strComponentsSeparatedByString]
__text:000DA162                 MOV             R2, (stru_587698 - 0xDA16E) ; "-"
__text:000DA16A                 ADD             R2, PC  ; "-"
__text:000DA16C                 BLX             _objc_msgSend
__text:000DA170                 MOV             R8, R0
; Call *IPScannerInfo."TSN"()
__text:000DA172                 MOV             R0, R5
__text:000DA174                 MOV             R1, R4
__text:000DA176                 BLX             _objc_msgSend
; Call *IPScannerInfo."Length"(), branch if it is not equal to 18...
__text:000DA17A                 LDR             R1, [SP,#0x78+strLength]
__text:000DA17C                 BLX             _objc_msgSend
__text:000DA180                 CMP             R0, #0x12
BNE             TSN_String_Invalid
; if(*IPScannerInfo."Length"() != 18){
; See if the count of R8 (TSN seperated by "-") is equal to 4, branch if it is not
LDR             R1, [SP,#0x78+strCount]
MOV             R0, R8
BLX             _objc_msgSend
CMP             R0, #4
BNE             TSN_String_Invalid
; NOTE: We branch here to the ELSE statement further below!
; Get the index [0] of the TSN split by "-", save it into valTSNPart1
; We also save an instance of the stringClass
MOVW            R0, #0xAC26
MOVS            R2, #0
MOVT.W          R0, #0x45
LDR.W           R11, [SP,#0x78+strObjectAtIndex]
ADD             R0, PC ; classRef_NSString
LDR             R0, [R0] ; _OBJC_CLASS_$_NSString
MOV             R1, R11
STR             R0, [SP,#0x78+refNSStringClass]
MOV             R0, R8
BLX             _objc_msgSend
STR             R0, [SP,#0x78+valTSNPart1]
; Get the index [1] of the TSN split by "-" (R8), save it into R10
; We also save R10 into R4 beforehand
MOV             R0, R8
MOV             R1, R11
MOVS            R2, #1
BLX             _objc_msgSend
MOV             R4, R10
MOV             R10, R0
; Get the index [2] of the TSN split by "-" (R8), save it into R6
MOV             R0, R8
MOV             R1, R11
MOVS            R2, #2
BLX             _objc_msgSend
MOV             R6, R0
; Get the index [3] of the TSN split by "-" (R8)
MOV             R0, R8
MOV             R1, R11
MOVS            R2, #3
BLX             _objc_msgSend
; Save our TSN Parts to memory
STR.W           R10, [SP,#0x78+refSelf] ; Save Index [1]
MOV             R10, R4 ; Restore R10 to be "self"
STR             R6, [SP,#0x78+valTSNPart2] ; Save Index [2]
STR             R0, [SP,#0x78+valTSNPart3] ; Save Index [3]
; Call StringClass.StringWithFormat("%@%@%@%@",TSNPart1)
; TODO - This doesn't make sense, why are we ignoring the other TSN parts?
LDR             R0, [SP,#0x78+refNSStringClass] ;
LDR             R1, [SP,#0x78+strStringWithFormat]
MOV             R2, (stru_58E7C8 - 0xDA1EE) ; "%@%@%@%@"
LDR             R3, [SP,#0x78+valTSNPart1]
ADD             R2, PC  ; "%@%@%@%@"
LDR.W           R11, [SP,#0x78+strRemoveObject]
LDR             R6, [SP,#0x78+refSuccessTCDs]
BLX             _objc_msgSend
; Call *IPScannerInfo.SetTSN(StringClass.StringWithFormat("%@%@%@%@",TSNPart1))
LDR             R1, [SP,#0x78+strSetTSN]
MOV             R2, R0
MOV             R0, R5
BLX             _objc_msgSend
; Determine the hardware type from the body id
; NOTE: We branch here if the ServerCertDescription is not empty
Determine_Hardware_Type
; Call _OBJC_CLASS_$_NetworkManager.HardwareTypeFromBodyId(*IPScannerInfo.Tsn(),0)
MOV             R0, (classRef_NetworkManager - 0xDA20E) ; classRef_NetworkManager
LDR             R1, [SP,#0x78+strTSN]
ADD             R0, PC ; classRef_NetworkManager
LDR             R4, [R0] ; _OBJC_CLASS_$_NetworkManager
MOV             R0, R5
BLX             _objc_msgSend
LDR             R1, [SP,#0x78+strHardwareTypeFromBodyID]
MOV             R2, R0
MOV             R0, R4
MOVS            R3, #0
BLX             _objc_msgSend
; If the hardware type returned is NOT 5...branch 
CMP             R0, #5
BNE             loc_DA274
; if(HardwareType != 5){
__text:000DA274 Not_Transcoder_Hardware
; NOTE: R0 is our hardware type enum value (1-5)
; TODO - This logic is confusing, try to figure out what actually has a "mind", is it 2, 3, or 4? or a combo?
; If HardwareType is <= 3, branch (it does not have a mind!)
__text:000DA274                 SUBS            R1, R0, #1 ; Add 1 to our enum value, store it in R1
__text:000DA276                 CMP             R1, #2 ; Subtract (HardwareType-1) from 2, set the carry bit if (HardwareType-1) > 2
BCC             loc_DA28C ; Branch if Carry is clear (we did not go negative)
; If HardwareType != 4, branch (it does not have a mind)
__text:000DA27A                 CMP             R0, #3
__text:000DA27C                 IT NE
__text:000DA27E                 CMPNE           R0, #4
__text:000DA280         BEQ             loc_DA28C
; If we got here, HardwareType is 4
; Call *IPScannerInfo.SetHasMind(1)
LDR             R1, [SP,#0x78+strSetHasMind]
MOV             R0, R5
MOVS            R2, #1
loc_DA288
BLX             _objc_msgSend
loc_DA28C
; Call *IPScannerInfo.SetLastConnection(null)
LDR             R1, [SP,#0x78+strSetLastConnection]
MOV             R0, R5
MOVS            R2, #0
BLX             _objc_msgSend
; Call *IPScannerInfo.Dump()
; TODO - What does dump do? Delete it?
LDR             R1, [SP,#0x78+strDump]
MOV             R0, R5
BLX             _objc_msgSend
B End_of_loop
; } else {
Transcoder_Hardware
; Call *m_successfulTranscoders.AddObject(*IPScannerInfo)
LDR             R0, [SP,#0x78+refSuccessTranscoders]
MOV             R2, R5
LDR             R1, [SP,#0x78+strAddObject]
LDR.W           R0, [R10,R0]
BLX             _objc_msgSend
; Remove from *m_successTCDs
LDR.W           R0, [R10,R6]
MOV             R1, R11 ; RemoveObject
MOV             R2, R5 ; *IPScannerInfo
B               loc_DA288
; }
; } else if (*IPScannerInfo."Length"() != 18 || *TSN."ComponentsSeparatedByString"("-").Length() != 4){
TSN_String_Invalid
; Call *IPScannerInfo.TSN().Length(), check if it is equal to 0, branch if it is (0)
LDR             R1, [SP,#0x78+strTSN]
MOV             R0, R5
BLX             _objc_msgSend
LDR             R1, [SP,#0x78+strLength]
BLX             _objc_msgSend
CMP             R0, #0xF
BEQ             Determine_Hardware_Type
; if(*IPScannerInfo.TSN().Length() != 0){
; BRANCH TO "Determine Hardware Type"
; }else{
; Cleanup, this is not a tivo!
Cleanup_Invalid_IPScannerInfo
; Retrieve the address for this IPScannerInfo
LDR             R1, [SP,#0x78+strAddress]
MOV             R0, R5
BLX             _objc_msgSend
MOV             R1, R0
; Log
MOV             R0, (cfstr_Removing - 0xDA264) ; "Removing %@"
ADD             R0, PC  ; "Removing %@"
BLX             _NSLog
; Call m_successTCDs.removeObject(*IPScannerInfo)
LDR.W           R0, [R10,R6]
MOV             R1, R11
MOV             R2, R5
BLX             _objc_msgSend
B               End_of_loop
; }
; }
; Increment the counter, if it is not 0, continue looping
End_of_loop
LDR             R2, [SP,#0x78+valIncrementerCount]
SUBS            R2, #1
CMP.W           R2, #0xFFFFFFFF
BGT.W           beginning_of_loop

; }
; Erase all items from the *m_connections array, normal cleanup

__text:000DA2AA loc_DA2AA
__text:000DA2AA                 MOV             R0, (_OBJC_IVAR_$_IPScanner.m_connections - 0xDA2BA) ; NSMutableArray *m_connections;
__text:000DA2B2                 MOVW            R1, #0x4A2C
__text:000DA2B6                 ADD             R0, PC  ; NSMutableArray *m_connections;
__text:000DA2B8                 MOVT.W          R1, #0x45
__text:000DA2BC                 ADD             R1, PC ; selRef_removeAllObjects
__text:000DA2BE                 LDR             R0, [R0] ; NSMutableArray *m_connections;
__text:000DA2C0                 LDR             R1, [R1] ; "removeAllObjects"
__text:000DA2C2                 LDR.W           R0, [R10,R0]
__text:000DA2C6                 ADD             SP, SP, #0x60
__text:000DA2C8                 POP.W           {R8,R10,R11}
__text:000DA2CC                 POP.W           {R4-R7,LR}
__text:000DA2D0                 B.W             j__objc_msgSend
__text:000DA2D0 ; End of function -[IPScan








; NOTE: NetworkManager."hardwareTypeFromBodyID:tsnIndex"() returns an enum based on the TCD value (before the first "-") that determines what type of box it is (transcoder vs others vs minds)

NetworkManager."hardwareTypeFromBodyID:tsnIndex"([String] (Server Certificate CN).Split('-')[0]) {

HardwareType Enum: {
0: NOT_A_TIVO
1: ??  - Guess: TIVO SD?
2: TIVO_HD
3: S3  - Guess : Tivo Series 3? TODO
4: Premiere - Guess : TIVO Premiere? TODO ; HAS MIND!
5: TRANSCODER_SUPPORT  - Guess : TIVO Stream;
}
Look up table
| TSN Value  | Enum |
-----------------------
1   1
2   1
3   1
4   1
5   1
627   1
640   1
649   1
653   1
648   2
652   2
658   2
663   2
6E8   2
6F2   2
6F8   2
A94   5
AD4   5
AE4   5
AF0   3
A90   3
A92   4
AE2   4
other   0
-----------------------
}