# H1-702 CTF 2017 - Write Ups

H1702 CTF was a CTF organized by hackerone. There were 6 Android and 6 iOS reverse engineering challenges. I finished 4th.

1. Android Write ups
2. iOS Write ups

# Android Write ups

## Level 1

Let’s start you off with something easy to get you started.
(Note: Levels 1-4 use the same application)
ctfone-490954d49dd51911bc730d8161541cf13e7416f9.apk

Since Level 1-4 uses the same APK, I am going to do a long writeup for the 4 challenges.

As with any other Android challenge, the first step is to decompile the APK using apktool as well as dex2jar.

After which, the next step would be to look at the AndroidManifest.xml file. Looking at the manifest file would allow you to identify entry points.

In this case, there is two other files we should focus on, com.h1702ctf.ctfone.MainActivity, the main class and com.h1702ctf.ctfone.Level3Activity, an additional activity class.

We first look at com.h1702ctf.ctfone.MainActivity, we can view the decoded smali code or the decompiled class file using JD-GUI or any other Java Decompiler you are familiar with.

Immediately in the onCreate method, we can see that two tabs are created with the labels Level 1 and Level 2. Following this lead, we immediately focus onto TabFragment1.class to look for leads that’d allow us to solve the first challenge.

Again, as the class is relatively simple, it is obvious that in the onCreate method, a certain resource is loaded depending on the value of a text input.

We can immediately see that if the input is empty, a random asset[1-10] is loaded. Following that hint, we can browse to the assets folder to look for the flag. Just by looking at the contents of the folder, we would be able to immediately identify the asset that’d give us the flag.

The first flag is immediately presented to us in the tHiS_iS_nOt_tHE_SeCrEt_lEveL_1_fiLE image file.

Flag #1: cApwN{WELL_THAT_WAS_SUPER_EASY}

## Level 2

Maybe something a little more difficult?

Following what we saw in challenge 1, we can immediately focus onto TabFragment2.class to try to solve for challenge 2.

Here we see that the method hashOfPlainText from the InCryption class is called. From the method name, it seems to be telling us that this function returns the hash of a certain Plaintext string.

Looking at the InCryption class, we can immediately recreate the class in Java to run it locally on our machine.

Running this, we obtain what appears to be morse code.

Converting this to real dots and dashes, we get the following morse code:

-.-. .- .--. .-- -. -... .-. .- -.-. -.- . - -.-. .-. -.-- .--. --... ----- -.... .-. ....- .--. .... -.-- ..- -. -.. . .-. ... -.-. --- .-. . .---- ..... ..- -. -.. . .-. ... -.-. --- .-. . .... ....- .-. -.. ..- -. -.. . .-. ... -.-. --- .-. . -... .-. ----- -... .-. .- -.-. -.- . -


The morse code is then translated to our second flag, cApwN{CRYP706R4PHY_15_H4RD_BR0}.

## Level 3

Think you can solve level 3?

Looking back, we can immediately focus onto Level3Activity.class to begin our hunt for the third flag. Also a very simple class, we can trace the onClick method to MonteCarlo.start().

In MonteCarlo, the class seems to be trying to approximate the value of Pi, which is consistent with what the name suggests. Everything else in the class seems normal except for a call to another class ArraysArraysArrays.start() and a JNI method being declared and not called anywhere.

Starting with ArraysArraysArrays, this class seems to be sorting arrays of integers and printing them after. However, again, a JNI method x() is declared, but is called in the start method this time.

To analyze the JNI method, we can view the libnative-lib.so file in IDA and focus on the method directly. The decompilation of the method is as follows:

Here it is obvious that some constant in the library is being decrypted with a fixed key. Using a quick Python script, we can quickly decrypt the constants.

Running the script gives us the following:

com/h1702ctf/ctfone/Requestor
request
()V


Together with the decompilation of the class, we can quickly infer that the JNI method x() is calling the request method located in com/h1702ctf/ctfone/Requestor. With that, we can quickly return to JD-GUI to look at the Requestor class.

In the Requestor class, we see that the method is used to make a request to https://h1702ctf.com/About and a custom header key/value is added with the values obtained through two other JNI methods hName and hVal.

Returning to IDA, we see that in the two JNI methods, constants are being decrypted with a fixed xor key again. Again, with a Python script, we easily decrypt the two constants to get the following:

X-Level3-Flag
V1RCR2QyUXdOVGROVmpnd1lsWTVkV1JYTVdsTk0wcG1UakpvZVUxNlRqbERaejA5Q2c9PQo=


It is apparent that the header value is a base64 encoded value. We can easily decode it to obtain our third flag!

In [6]: "V1RCR2QyUXdOVGROVmpnd1lsWTVkV1JYTVdsTk0wcG1UakpvZVUxNlRqbERaejA5Q2c9PQo=".deco
...: de('base64').decode('base64').decode('base64')
Out[6]: 'cApwN{1_4m_numb3r_7hr33}\n'


## Level 4

Challenge 4 stumbled me for awhile, it wasn’t as obvious as to where to begin as compared to the previous 3 challenges. However, recalling the unused JNI method declared in the MonteCarlo class, it soon became apparent to me that the solution of Challenge 4 would be given by invoking the functionnameLeftbraceOneCommaTwoCommaThreeCommaRightbraceFour class with the flags of the first 3 challenge.

There is two way to approach this:

1. Create an APK to call the method in the native library directly.
2. Use frida to invoke the method with the correct parameters.

As I do not have frida readily available beside me, I chose to go with method #1. Using a simple template provided by Android Studio, I quickly write up a simple APK that loads the same library and calls the method with the flags as parameter.

Note that we’d have to replicate an APK with the same package name and class in order for this method to work smoothly.

With that, we obtain our 4th flag!

4th Flag: cApwN{w1nn3r_w1nn3r_ch1ck3n_d1nn3r!}

## Level 5

Hmmm… looks like you need to get past something…
ctfone5-8d51e73cf81c0391575de7b40226f19645777322.apk

Similarly, I start with decoding/decompilation of the APK with apktool and dex2jar.

Looking at the AndroidManifest.xml,

We see that there is an additional service linked to the class com.h1702ctf.ctfone5.CruelIntentions. From the unpacked APK, we can also find a native library libnative-lib.so.

With that, we first jump to the MainActivity class. From the dex2jar decompiled code of the class, we can immediately notice a few things. First, there is a hint that says State the secret phrase (omit the oh ex), where oh ex seems to refer to 0x, something you usually find before a hexadecimal representation of a value. Second, there is a JNI method defined flag, which seems to be taking in three String values and returning the flag is the values are correct, this seems to remind me of Challenge 4 previously.

The next step would be to look at the JNI method flag in IDA Pro. The decompilation of the method in IDA Pro appears to be very similar to challenge 4 previously as well. It seems to be using the blake2b hash values of the three strings and xor’ed together using the stream_xsalsa_xor with an unknown value (which is probably the encrypted value of the flag). Therefore, if the three strings provided were accurate, the result from this function would likely give us the flag for this challenge.

With that, we would need to figure out the three Strings required.

We now move on to look at com.h1702ctf.ctfone5.CruelIntentions. The decompiled class is pretty simple, the class is a subclass of IntentService and is used to set up an intent service which reacts to the com.h1702ctf.ctfone5.action.HINT action and calls a JNI method one() if the com.h1702ctf.ctfone5.extra.PARAM1 param is equal to orange.

Knowing this, we can call the intent using the following ADB command:
adb shell am startservice -n "com.h1702ctf.ctfone5/com.h1702ctf.ctfone5.CruelIntentions" -a com.h1702ctf.ctfone5.action.HINT -e com.h1702ctf.ctfone5.extra.PARAM1 orange

I tried running the intent service, but nothing appears to be happening. We did notice this in the logcat output.

I/BOOYA   ( 5120): got intent
I/BOOYA   ( 5120): got hint
I/BOOYA   ( 5120): param: orange


This indicates that the intent service was successfully invoked.

With the jar side of things covered (i.e. we have looked at all custom Java classes), we can now move to the JNI method one().

The IDA Pro decompilation of the method is long and complex therefore I will talk about snippets that appears interesting to me.

The first interesting snippet is:

This code snippet seems to check if the process is dumpable, and if so, sets it to be not dumpable.

The second interesting snippet is:

This portion seems to be checking if the TracePid of the process is non-zero, which hints whether the process is being traced/debugged.

The third interesting snippet is:

This portion seems to be checking if filepaths specified in g_su_paths is accessible.

And the filepaths are:

/su/bin/su
/system/bin/daemonsu
/system/xbin/daemonsu
/sbin/su
/system/bin/su
/system/xbin/su
/data/local/xbin/su
/data/local/bin/su
/system/sd/xbin/su
/system/bin/failsafe/su
/data/local/su


The fourth snippet is:

This portion seems to check that a certain system property mobsec.setme is equal to 1 and to return a certain value.

Putting the four parts together, it seems that the function is checking if the device is rooted or being traced/debugged and the fourth part appears to be a bypass for the check.

However, looking carefully, we can see that the return value of this native function is always a constant when the constraints are met.

.text:00002920                 LDR             R0, =0x5F53D58F
.text:00002922                 LDR             R3, =0x5F53D58F
.text:00002926                 LDR             R1, =0x7D670F2A
.text:00002928                 LDR             R3, =0x7D670F2B
.text:0000292C                 LDR             R2, =0x6D3D5D2F
.text:0000292E                 LDR             R3, =0x6D3D5D2F
.text:00002932                 LDR             R3, =0x6F56DD5F
.text:00002934                 LDR.W           LR, =0x6F56DD5F
.text:0000293A                 BX              LR


If we were to evaluate the ADD instructions, this is what we will obtain:

Therefore, it becomes apparent that this three values are the String values that is to be submitted to the flag() function. Attempting to submit this, we get our fifth flag! Remember that we are suppose to omit the 0x.

Our 5th Flag: cApwN{sPEak_FrieNd_aNd_enteR!}

## Level 6

I can’t think of anything creative… just try to solve this one :)
ctfone6-6118c10be480b994654a1f01cd322af2df2ceab6.apk

The AndroidManifest.xml doesn’t show any additional service or activity this time round other than the MainActivity. There is however a native library called libidk-really.so.

Also, within /res/raw there is two files called something.jar and secretasset.

The name itself seems to hint that they have something to do with the flag. Though its called something.jar, the file command doesn’t seem to recognize it as a legitimate file.

Looking at MainActivity.class, we see that the onClick event attempts to open the something.jar file and to execute it after it has been handled by the prepareDex method.

From the smali code, we can see that in the prepareDex method, something.jar is being decrypted using the following decryption method.

However, the key and IV is not clearly identified in the decryption method.

By looking again at the smali code, we see that the String resource with id 0x7f050001 and 0x7f050004 is used as parameter to the decrypt method, which means that the key and IV is hard coded.

With that, we can cross reference from /res/values/ids.xml to figure out that 0x7f050001 is referring to booper and 0x7f050004 is referring to dooper.

The contents of /res/values/strings.xml then tells us the String values. Using the decompiled code for the decrypt method, I quickly wrote a Java console application to decrypt something.jar.

Running the Java program gives us the decrypted something.jar file.

We then use dex2jar to decompile the decrypted jar, this gives us two classes, IReallyHaveNoIdea.class and Pooper.class.

IReallyHaveNoIdea.class seems to be registering a new intent receiver for the com.example.asdf.SEND intent. Pooper.class seems to be the receiver for the intent and appears to be decrypting something when it receives an intent call.

The onCreate function looks for the super-dooper file and then tries to decrypt it based on the params herpaderp and lerpaherp. There is however, a check for the two values and this might be the constraint that we have to solve to get the decryption key.

Looking at checkSomething1 and checkSomething2, we can slowly reverse the smali code which is iterating each character in a switch case and checking if the index of the character matches the value expected. With some work, we obtained b1ahbl4hbl4hblop for checkSomething1 and mmhmthisdatgoods for checkSomething2.

Though we are stuck without the super-dooper file, a simple test with secretasset shows that this decryption is meant for it. Again, using the same decryption Java program above, but with the new key/IV, we obtained a ELF binary.

Throwing the binary in IDA pro, we immediately see that a couple of interesting libc methods are imported like bind, accept and listen, this seems to hint that the program might be communicating through the network.

With that in mind, I ran the APK application, fed the text input with UCFh%divfMtY3pPD, which the process then proceeds to decrypt something.jar and sets up the intent receiver. Following that, I invoke the intent with the following command, adb shell am broadcast -a com.example.asdf.SEND -e herpaderp b1ahbl4hbl4hblop -e lerpaherp mmhmthisdatgoods to start the execution of the ELF binary.

Once that is in place, a simple netstat shows that a service is indeed listening on port 1337.

We can attempt to communicate with it using netcat:

Looking at the bind method in IDA pro, we can trace the code to the method located at 0x00001B10, the method which handles all data received by the socket.

The method appears to be decrypting string constants with static xor keys and with some effort, we can figure out what the method is doing. For most part of the method, the procedure seems to be doing nothing malicious. However, for \PRIVATE, there appears to be a hidden procedure that resemblance a backdoor.

The \PRIVATE procedure calls a method located at 0x00001820 that takes in the reference and message as parameter.

In the method, it appears that if the reference equals to 1337, the backdoor is activated. The decrypted String constants in the method is gettin it done and Nice one!.

This portion of the method seems to be checking that the message provided starts with gettin it done and then appears to be checking each character after with against a hash value (by first hashing each character and checking against a hash stored in the binary). If the hash matches, it writes Nice one! to the socket response.

With that, I wrote a simple Python script that bruteforces each printable character until we obtain our 6th flag!

Our 6th and Last flag for Android: cApwN{d3us_d3x_my_4pk_is_augm3nted}

# iOS Write ups

I solved most of the iOS challenge with snoop-it, mobile assistant, jailbroken 32-bit device and an un-jailbroken 64-bit device.

## Level 1

WAKE ME UP, WAKE ME UP INSIDE. SAVE ME!!!!!
(Note: Levels 1-4 use the same application)
IntroLevels-727e07e27199b5431fccc16850d67c4fea6596f7.ipa

We can unzip the IPA file with an unarchiver tool to attempt to see if there are any asset or files which may be of interest. After which, we can use the cartool from https://github.com/steventroughtonsmith/cartool to decode the Assets.car file.

Viewing the resources, we found our first flag in Matrix-Morpheus.png.

Flag #1: cApwN{y0u_are_th3_ch0sen_1}

## Level 2

And he prays…

For Challenge 2, we can trace the ViewController for Level 2 to a method located at 0xA930, this method seems to be checking the hash value of the text input with 5b6da8f65476a399050c501e27ab7d91 before appending 1234 and then using it as a key to decrypt some constant values.

Googling 5b6da8f65476a399050c501e27ab7d91 immediately gives us the MD5 Plaintext value of 424241, and entering the plaintext value into the level 2 view gives us our second flag!

Flag #2: cApwN{0mg_d0es_h3_pr4y}

## Level 3

Rock, paper, scissors is so juvenile. Play rock, paper, scissors, lizard, Spock!

Again, in challenge 3, we can use the snoop-it application to attempt to figure out what the third view controller is doing.

When viewing the class, we notice that the view controller has a number of methods like setLizardImage, reportBeingALoser and so on. However, invoking the things method gave us our third flag.

Flag #3: cApwN{1m_1n_ur_n00twork_tere3fik}

## Level 4

Use your flags from levels 1, 2, and 3 to do the thing!

Challenge 4 appears to be telling us to use the first three flags to do the thing. This reminds me of the Android Challenge 4 where the first three flags are used to obtain the fourth flag.

Looking at the functions defined in IDA Pro, it seems like we are suppose to invoke the ZhuLi.doTheThing method.

Using cycript, we can easily invoke the method with our three previous flags, giving us our fourth flag!

Flag #4: cApwN{f0h_sw1zzle_my_n1zzle}

## Level 5

Looks like this thing is pretty locked down, I don’t think you can touch this.
Level5-69c2713162cb8f5e9418f8c08f3fa0a1ecb4928d.ipa

Throwing Level5Demo into IDA Pro, we can first see three custom methods which appears to be meddling with Keychain objects.

Also, there are a couple of interesting Strings being defined.

__cstring:00011680 00000014 C com.uber.ctf.level5
__cstring:00012280 00000012 C setmeinurkeychain
__cstring:00012292 0000000D C youdidathing


Tracing youdidathing, we reach a method located at 0xa180, this method appears to be searching the Keychain for the key setmeinurkeychain and then checking if the data value matches youdidathing.

With that, we can again make use of cycript to create the required Keychain items to see if this gives us our flag.

The following cycript script sets the required items.

We can also notice with snoop-it that there indeed was a keychain request for the key setmeinurkeychain.

After which, when we click the Hammer time! button, the app no longer crashes.

This gives us our fifth flag!

Flag #5: cApwN{i_guess_you_can_touch_this}

## Level 6

Hey look at me im Tiny Rick! Yeah now that I got your attention, I got this app here that Squanchy squanched on my phone. Looks like there is something in there… But I don’t give a @#\$! I’m Tiny Rick!
Level6-679e59bdfb40233fb1359d098d7269a3320eabd2.ipa
Update: This challenge did not function properly on iOS 32bit devices, here is the updated challenge Level6-update-f0887a253daaa02e584bc9ff4edfeca1300887dc.ipa
Note: The original version of the app is still solvable. The update is only for those who wish to run the app on a 32bit device.
Update: If you are attempting to solve the 32bit challenge and running into issues, contact @suspiciousfudge on the Slack channel

***I noticed that the 32-bit version was not solve-able and so I told @suspiciousfudge and he updated the binary after.

For the last iOS Challenge, running the app displays a text input that appears to be encoded with some sort of huffman tree.

We then throw the binary into IDA Pro. We see a couple of class being defined, MrPoopyButtHoleThing, MrPoopyButtholeWasInnocent and MrPoopyButtholeGappingOrifice, this three methods seems to be the binaryheaptree used to build the huffman tree.

We are then able to find a suspicious String constant That's Right Morty! This is gonna be a lot like that. Except you know. Its gonna make sense. and

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque suscipit, ligula vitae fringilla fringilla, lectus tortor eleifend ligula, vitae sodales mauris nibh a elit. Maecenas nec pellentesque massa. Curabitur volutpat lobortis risus id aliquet. Donec eget viverra enim. Etiam massa nibh, lobortis id pretium quis, consequat ut libero. Integer aliquet lacinia ex sed porttitor. Maecenas auctor eget nisl et mollis. Mauris et suscipit lorem, a facilisis magna. Etiam at facilisis lacus, mollis rhoncus lorem. Morbi vitae volutpat lectus. Donec ut vestibulum justo. Nullam ullamcorper ligula vel dignissim viverra. Quisque mi sapien, auctor quis quam ac, gravida ullamcorper purus. Morbi ut mi vitae massa dapibus rhoncus sed ut ipsum. Suspendisse accumsan dui at velit ultrices, ac hendrerit metus ullamcorper.Duis volutpat condimentum faucibus. Aliquam ex nisl, sodales in urna vel, vestibulum faucibus metus. Donec dapibus ante magna, luctus hendrerit felis commodo vitae. Vivamus quis sodales quam. Nullam dictum venenatis eros, vitae feugiat erat sollicitudin eu. Mauris aliquam, purus id porta porttitor, ligula felis egestas ex, non feugiat urna sem a nisi. Nunc eget tincidunt lorem, et dictum diam. Integer sodales tempus finibus. Donec pharetra ut risus sit amet bibendum. Morbi molestie lacinia varius. Duis diam dui, pulvinar non orci a, malesuada dictum metus. Morbi semper at ante in dignissim. Maecenas at molestie nibh. Mauris sollicitudin, ipsum eu imperdiet tristique, neque purus tristique sem, quis porta leo libero et orci. Fusce sed odio lobortis, pharetra justo et, tristique mauris. Vestibulum in interdum libero, et euismod lacus. Nulla volutpat pulvinar tortor at placerat. In non magna eget nibh egestas lacinia eleifend eu metus. Nullam ac mattis nisi. Curabitur porttitor enim sed elementum interdum. Duis sed molestie enim. Nullam varius ex efficitur efficitur mollis. Vestibulum in sollicitudin erat. Quisque in turpis eget leo eleifend ultricies at blandit arcu. Vivamus at pretium quam. Praesent laoreet ligula faucibus ante tincidunt, in euismod massa auctor.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_


The second string appears to be the string for which the huffman tree is built on. I proved my hypothesis by attempting to encode characters that doesn’t exist within the second string, and for those characters, the encoding fails. Also, we see that at the end of the string, there is the {}_, those are common characters found in CTF flags and was the initial reason for my suspicion.

With that being said, we are able to trace the Morty message to a method located at 0xA514. In the method,

It seems like our encoded input is fed to the method sub_AE7A, before being compared to the 0x130 sized byte array byte_1550C. However, attempts to reverse engineer sub_AE7A proves to be too complex, and so I chose to go the path of dynamic analysis.

Since the encoded input is thrown into sub_AE7A, the encoded input string should be 1 or 0, therefore, I need to provide a plaintext input that gets encoded to a string of 1 or 0 only with a length of 304 characters.

With that being said, I found that e is encoded to 1111, and so, 76 e’s would give us an encoded string of 304*1. And so, Making us of debugserver and lldb, I set a breakpoint at 0x0000A780 and dumped the result of the method call for sub_AE7A.

After some testing, I found out that the encoded string is xor’ed with some unknown values and then compared to byte_1550C, and so, with the dumped values from before, I wrote a quick Python script to xor each byte with 0x31 which is the hexadecimal representation of 1, and then comparing this with byte_1550C, I obtained the encoded string value which we are suppose to provide to solve this challenge.

Running this Python script gave us our last flag!

Flag #6: cApwN{1m_mr_m33s33ks_l00k_at_meeeeeeeeeee}

### Extra information

Notice that in my Python Script, the huffman tree is obtained in a pretty hackish way, this way due to the errors in the 32-bit binaries at the start. I then noticed that with the huffman tree of the 64-bit binary, I’d then be able to get cApwN at the start of the encoded flag and so, I devised a way to obtain the 64-bit binary huffman tree.

The reason why I was not able to use snoop-it was because I do not have a jailbroken 64-bit device.

To obtain the huffman tree for 64-bit, I crafted a plaintext string that contains every possible character needed in a normal flag.

The list of possible characters are: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_

In order to delimit the encoding, I used } as a delimiter inbetween each character. As } is located at the bottom of the huffman tree (based on playing around and experimenting), we are able to confidently delimit the encoded huffman code to obtain the accurate 64-bit huffman tree values for each character.