For this challenge we’re provided with an iOS “app” file. These files are archives containing resources, the actual Mach-O binary, and other related files.

We can load the app file into an iOS simulator and see what happens when we run it to determine if there are any good starting locations for static analysis.

Using Xcode, I created a blank project and launched the iOS simulator as an iPhone 6s device, then I installed the app with the following command:


xcrun simctl install booted showmewhatyougot.app

Upon initial launch, we’re prompted to allow the application access to location data, which is a good indicator that GPS may be involved.

The first screen of the application says “What You Got Go Here” (input) and then when you get it wrong it responds with an error message.

Alright, so the first thing that should stand out is the error message “Boo! Not Cool!”, which is a good string to begin our analysis on; we can then work backwards from there.

If you look for the strings in the Mach-O file via IDA, nothing immediately stands out in plain text but there are a couple of base64 encoded values that stand out like a sore thumb; additionally, we see things like “LabelBox” and “TextBox” shown in the strings table that correspond to the previous Swift prompt for input and its returned message.

Decoding some of the base64 values doesn’t reveal anything of note on first glance.


>>> base64.b64decode('Y05OAAFvTlUBYk5OTQA=')

'cNN\x00\x01oNU\x01bNNM\x00'

>>> base64.b64decode('QHlITQ==')

'@yHM'

>>>
base64.b64decode('UjtXUlBeO0xTWk87QlROO1xUTzU7XFRUXztRVFk1')

'R;WRP\;LSZO;BTN;\\TO5;\\TT_;QTY5'

>>> base64.b64decode('IiY/IyIlIiIj')

'"&?\"%""\'

>>> base64.b64decode('OiYmIjkvJyEhISQ=')

':&&"9/\'!!!$'

If we start with the longest of the base64 strings, we find ourselves in function “sub_100001330” at block “loc_10000205E”. We can see it’s being passed to the Foundation NSData base64Encoded function, which returns the decoded data.

Assuming these are messages, such as the “Boo! Not Cool!”, then it seems there is additional data manipulation in play after it gets base64 decoded. A quick check for simple 1-byte XOR finds reveals what we’re hunting for.


b64msgs = ["eENJQVl6QkVETw==",

"IiY/IyIlIiIj",

"QHlITQ==",

"GQcY",

"Y05OAAFvTlUBYk5OTQA=",

"OiYmIjkvJyEhISQ=",

"UjtXUlBeO0xTWk87QlROO1xUTzU7XFRUXztRVFk1"]

def xorbrute(a):

c = base64.b64decode(a)

count = 0

while count < 256:

b = ''

for i in c:

b += chr(ord(i) \ count)

if "Boo!" in b:

print "Found in: %s\nXOR: %s\nMSG: %s" % (a,count,b)

count += 1

for i in b64msgs:

xorbrute(i)

Results in…


Found in: Y05OAAFvTlUBYk5OTQA=

XOR: 33

MSG: Boo! Not Cool!

Manually reviewing the other base64 decoded/XOR’d results show a few obvious strings (all with different XOR-byte values).


Found in: UjtXUlBeO0xTWk87QlROO1xUTzU7XFRUXztRVFk1

XOR: 27

MSG: I LIKE WHAT YOU GOT. GOOD JOB.

Found in: eENJQVl6QkVETw==

XOR: 42

MSG: RicksPhone

Found in: QHlITQ==

XOR: 41

MSG: iPad

A “win” message, along with a string that references “RicksPhone”, and finally one referring to “iPad”. Looking at the calls being made within IDA helps put some context around the messages.

Along with some less obvious ones.


Found in: OiYmIjkvJyEhISQ=

XOR: 23

MSG: -115.806663

Found in: IiY/IyIlIiIj

XOR: 17

MSG: 37.234332

Which are GPS coordinates. These were more difficult to eyeball raw but, again, seeing the context and calls before the strings give insight into their meaning.

Along with…

The last one is a little trickier to eyeball but the call before it’s used show it’s pulling the battery level for the current device.

The only XOR’d value that is valid (0.0 – 1.0 per Apple) is found with integer key 41.


Found in: GQcY

XOR: 39

MSG: > ?

Found in: GQcY

XOR: 40

MSG: 1/0

Found in: GQcY

XOR: 41

MSG: 0.1

Found in: GQcY

XOR: 42

MSG: 3-2

Found in: GQcY

XOR: 43

MSG: 2,3

Found in: GQcY

XOR: 44

MSG: 5+4

To recap then, we know the app is likely checking for, and comparing against, the device values for GPS coordinates, device name, device model, and battery level.

The next step is to determine their purpose…

If we look at the function “_TtC16showmewhatyougot14ViewController - (void)SubmitButton:(id)” you’ll see a call in loc_1000035AB to the another function at sub_100002EA0. This function (2EA0) contains all of the base64 decoding calls from the screenshots above so it seems safe to assume that when you submit input these will get checked.

Below is the order in which these appear to get assessed and the expected values.


currentDevice = RicksPhone

coordinate1 = 37.234332

coordinate2 = -115.806663

deviceModel = iPad

batteryLevel = 0.1

At the bottom of this function is the “lose” message. It seems likely that this function is responsible for the comparison of each value and then, assuming one doesn’t pass, will display the “Boo! Not Cool!” message.

The strange part is, when analyzing this function, there doesn’t appear to be a call to the “win” function, regardless if you pass the checks.

Pivoting over to the “win” function in IDA, the only cross reference is to a function with the following label – “; _TtC16showmewhatyougot14ViewController - (void)didReceiveMemoryWarning”.

The Apple Developer site has this to say about that call: “Your app never calls this method directly. Instead, this method is called when the system determines that the amount of available memory is low.”

Based on that, I don’t believe you would be able to get to this function natively without some kind of memory issue.

In the “win” function there are a number of calls to the same methods in the “lose” function that return data about the device (eg coordinates, currentDevice, deviceModel, batteryLevel).

Additionally, we see reference to CryptoSwift AES, informing us that we’ll likely be dealing with this at some point.

A quick Google lands you on this CryptoSwift library, which supports AES and by default uses PKCS7 with CBC mode. They have a fairly straight forward example of how the Swift code implementation might look.


let input: Array<UInt8> = [0,1,2,3,4,5,6,7,8,9]

let key: Array<UInt8> =
[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]

let iv: Array<UInt8> = AES.randomIV(AES.blockSize)

do {

let encrypted = try AES(key: key, iv: iv, blockMode: .CBC, padding:
PKCS7()).encrypt(input)

let decrypted = try AES(key: key, iv: iv, blockMode: .CBC, padding:
PKCS7()).decrypt(encrypted)

} catch {

print(error)

}

It seems plausible then that to solve this we need to determine the Key and IV values, which are probably related to the decoded/XOR’d values from before, and then decrypt the LabyREnth key.

At this point I opted to try and debug it as I wasn’t having much luck statically in IDA.

To debug it, I used Xcode again as I already had the app loaded up from earlier to see what it actually did. Simply attaching to the running process (showmewhatyougot) within Xcode allows us to begin debugging from there.

*NOTE: I crashed the app many times throughout this debugging so addresses were randomized on each launch and screenshots may jump around some from that perspective. I’ve tried to highlight the important bits but I’m also going to be overly verbose for this write-up since I spent days suffering through it…deal with it!

Alright, before diving into it it’s worth mentioning Xcode’s simulator has the ability to use custom locations, so right off the bat we can enter the GPS coordinates discovered earlier.

The next step is to figure out how to navigate around the app and orient ourselves within the code. Knowing that strings get base64 decoded, we can start with setting a Symbolic breakpoint (Debug -> Breakpoints -> Create Symbolic Breakpoints…) on “initWithBase64EncodedString:options:”.

Almost immediately you’ll hit it and pause the application.

Next, stepping out of the current function twice with F8 will land us in the main application code and we can find where we’re at within IDA to assist with analysis.

The function we’ve landed in is actually one that hadn’t been discussed earlier. It’s a thread that runs in the background and constantly checks the GPS coordinates; however, it suites our needs just fine.

If you go to the top of the function and look at the address, we can calculate the address of the “win” function by subtracting 0x5CE0 from it.


coordcheck == sub_100007810 == 100ed5810

devicecheck == sub_100002EA0

windecrypt == sub_100001B30 == 100ecfb30

In the bottom-right of the debugger is an LLDB window that we can use to quickly verify the address we’re interested in pivoting to at 0x100ECFB30 is correct and then set a breakpoint.

For reference, this GDB to LLDB cheat sheet was extremely helpful.

Once the breakpoint is set, we need to trigger a memory warning – luckily, Xcode has that covered as well (Hardware -> Simulate Memory Warning).

Disabling the base64 breakpoint, resuming the program, and then activating the memory warning lands us exactly where we want.

Ok, so going back to the calls that poll device data, there are two separate blocks that collect information. The first one pulls the first coodinate, deviceModel, and then currentDevice (name) whereas the second block pulls the second coordinate, deviceModel, and finally batteryLevel.

The below image shows the first.

After it has these values it passes them to the function located at sub_1000043B0. This functions purpose is to take each value and append them to a string and then return a pointer to it via the RAX register. If you set a breakpoint on the return instruction (“retq”) you can see the string prior to it being returned.

Below is an example of the second block with coordinate 2, deviceModel, and batteryLevel. The “-1.0” is due to battery monitoring not being enabled and so this is the value returned when it’s an unknown (batteryLevel).

What’s nice about this is we can see exactly how the string is built and concatenated; however, there doesn’t seem to be a way to spoof the device name or battery level. Given this, we can try and re-write the memory for each value before it’s returned.

After trying, and failing, to get the “size” parameter to work with “memory write” we can take the lazy approach and write each byte individually (copy/pasta with find/replace on each run for new addresses).

And…

While this appeared to work at first, there was something amiss because issues began cropping up later; however, knowing the formatting of the string is the most import piece.

Once it has these two strings, it moves into generating a CRC32 hash for each of them.

But this is where the first issue arises and things get funky – that’s not the correct CRC32 for that string.


>>> hex(binascii.crc32('37.234332iPadRicksPhone'))

'0x23c150cd'

Similarly, for the other modified string…


(lldb) x/1s $rcx

0x610000067320: "-115.806663iPad0.1"

(lldb) x/1s $rax

0x61000004b810: "289e426a" ; WRONG

>>> hex(binascii.crc32('-115.806663iPad0.1'))

'0x17017b6c'

There are other CRC32 hashes generated later that come out correct so it didn’t seem like the CryptoSwift implementation was wrong - most likely the way the memory was modified was the culprit. Possibly the size of the string was stored somewhere and referenced later? Another oddity is that the CRC32 changed on each subsequent run, even when the input was the same, so that’s problematic…shrug.

Pushing on, I chose re-write these in memory too…something something about those who do not learn history are doomed to repeat it…

And…

Once it has these two CRC32 values, it appends them together in the below block so we now have a value “23c150cd17017b6c”.

The next piece of code we come to in the debugger base64 decodes/XOR’s a previous value we saw (“QHlITQ==” or “iPad”).

After each decoding it takes the value “iPad” and generates the correct CRC32 for it, confirming something was likely not accounted for with the way memory got modified earlier.


(lldb) x/1s $rax

0x61800004fdd0: "3defb682"

(lldb) x/1s $rax

0x61800004fe90: "3defb682"

>>> hex(binascii.crc32('iPad'))

'0x3defb682'

Once it has the CRC32 for both, it reverses the second one in sub_100004B50 and appends them together.

This is where another debugging oddity reared its ugly head unfortunately. On each run when it would return the concatenated CRC32’s (one reversed) but the RAX register was pointing 8 bytes into the string, which always ended up returning the wrong value.


(lldb) x/1s $rax

0x618000070e28: "286bfed3\xffffff80Q\x03"

(lldb) x/5s 0x618000070e20

0x618000070e20: "3defb682286bfed3\xffffff80Q\x03"

0x618000070e34: "\xffffff80\`"

0x618000070e37: ""

0x618000070e38: ""

0x618000070e39: ""

Below is a different run where after manually editing the memory (YES I KNOW I AM A GLUTTON FOR PUNISHMENT) by aligning the RAX register back 8 bytes to the start of the string I inject a null byte to terminate the string where it should be.

At this point we at least know of a second value, “3defb682286bfed3”.

This brings us to loc_100002351 in which some really ugly compiled and optimized assembly simply builds three arrays.

Below is a quick break down of each part to better illustrate it.

Effectively, it uses the xmm0 array to clear space and then at R15+0x20 it moves the bytes into position. This one is fairly straight forward.

This gives us the following value:


\x63\x71\xF0\xE8\xF2\xEB\xA5\x01\xCB\x6D\x5D\x99\x9C\xE2\x2E\x9D

The next one is similar except it copies into xmm0 two arrays that it then moves into the middle of the current byte array it’s building. The reason it optimized like this, I believe, is due to the final size of the array being 48 bytes and saving instruction space by storing 32 bytes elsewhere in pre-defined vectors.

Either way, once it completes, we end up with the following value:


\x47\x45\x44\x59\xD8\x74\x85\x93\x82\x0F\x2A\x2A\x21\x20\x30\x6F\x28\x89\x64\x20\xFA\xFB\xB9\xB1\x4F\x7F\x5B\x81\x24\xE0\x20\x18\x19\x65\x4E\x29\x02\xFE\xB4\xE8\xFA\x96\x35\x03\x92\x38\x8D\xD5

Finally, the third one.

Which gives us the below value:


\xD2\x2B\x2C\x22\x36\x3B\xFD\x1B\x80\x86\x65\x3A\xEF\xBC\xCB\x34

After these are created it moves into the CryptoSwift.AES initialization, which can be seen in the previous screenshot. Once it initializes AES it will branch off into three decryption routines (remember the 3 arrays?) and once it decrypts the data it calls ViewController_TextBox.

Try as I might, I was never able to get it to successfully decrypt via this method of debugging and would either receive exceptions, such as “EXC_BAD_ACCESS”, or the entire app would crash and I’d be greeted with something like the below…

I was really hoping to do everything inside the debugger but, alas, we have enough information to tackle the final piece outside of it.

We know the first value created is “23c150cd17017b6c” and the second value created is “3defb682286bfed3”. We have three arrays of bytes and know how CryptoSwift.AES is called. Writing a quick little Python script to put all the pieces together reveals the answer we’re after.


from Crypto.Cipher import AES

pleaseletthisbeover =
['\x63\x71\xF0\xE8\xF2\xEB\xA5\x01\xCB\x6D\x5D\x99\x9C\xE2\x2E\x9D',
'\x47\x45\x44\x59\xD8\x74\x85\x93\x82\x0F\x2A\x2A\x21\x20\x30\x6F\x28\x89\x64\x20\xFA\xFB\xB9\xB1\x4F\x7F\x5B\x81\x24\xE0\x20\x18\x19\x65\x4E\x29\x02\xFE\xB4\xE8\xFA\x96\x35\x03\x92\x38\x8D\xD5',
'\xD2\x2B\x2C\x22\x36\x3B\xFD\x1B\x80\x86\x65\x3A\xEF\xBC\xCB\x34']

def decrypt(a):

aes_ha = AES.new("23c150cd17017b6c", AES.MODE_CBC, "3defb682286bfed3")

ohmyglob = aes_ha.decrypt(a)

print ohmyglob

for i in pleaseletthisbeover:

decrypt(i)

Resulting in…

The challenge key is “PAN{153dc54a34644c05c9c590ba365c2069}”.