
[{"content":"","date":"6 August 2026","externalUrl":null,"permalink":"/","section":"Curiosity With a Side of Chaos","summary":"","title":"Curiosity With a Side of Chaos","type":"page"},{"content":"","date":"6 August 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"16 July 2017","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"Welcome to the giant, rather disorganized, all-in-one writeup for all the challenges I completed during the H1702 CTF this year. I was able to complete all of the levels except for the 6th for both Android and iOS.\nForeword # These will likely be updated again as these are pretty much copied 1 for 1 from the writeups I submitted along with the CTF flags. Ideally I would like to add in some screenshots, go more in-depth for the iOS writeups, and make some other minor changes.\nChallenge Files # Android # Levels 1-4 (SHA1: 490954d49dd51911bc730d8161541cf13e7416f9) Level 5 (SHA1: 8d51e73cf81c0391575de7b40226f19645777322) Level 6 (SHA1: 6118c10be480b994654a1f01cd322af2df2ceab6) iOS # Levels 1-4 (SHA1: 727e07e27199b5431fccc16850d67c4fea6596f7) Level 5 (SHA1: 69c2713162cb8f5e9418f8c08f3fa0a1ecb4928d) Level 6 (SHA1: f0887a253daaa02e584bc9ff4edfeca1300887dc) Hints # Android # Level 1: \u0026ldquo;Let\u0026rsquo;s start you off with something easy to get you started.\u0026rdquo; Level 2: \u0026ldquo;Maybe something a little more difficult?\u0026rdquo; Level 3: \u0026ldquo;Think you can solve level 3?\u0026rdquo; Level 4: \u0026ldquo;Hope you kept your notes.\u0026rdquo; Level 5: \u0026ldquo;Hmmm\u0026hellip; looks like you need to get past something\u0026hellip;\u0026rdquo; Level 6: \u0026ldquo;I can\u0026rsquo;t think of anything creative\u0026hellip; just try to solve this one :)\u0026rdquo; iOS # Level 1: \u0026ldquo;WAKE ME UP, WAKE ME UP INSIDE. SAVE ME!!!!!\u0026rdquo; Level 2: \u0026ldquo;And he prays\u0026hellip;\u0026rdquo; Level 3: \u0026ldquo;Rock, paper, scissors is so juvenile. Play rock, paper, scissors, lizard, Spock!\u0026rdquo; Level 4: \u0026ldquo;Use your flags from levels 1, 2, and 3 to do the thing!\u0026rdquo; Level 5: \u0026ldquo;Looks like this thing is pretty locked down, I don\u0026rsquo;t think you can touch this.\u0026rdquo; Level 6: \u0026ldquo;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\u0026hellip; But I don\u0026rsquo;t give a @#$! I\u0026rsquo;m Tiny Rick!\u0026rdquo; Android Level 1 # Decompilation # First, I decompiled the application fully. To do this, I used apktool, dex2jar, and procyon.\nThe flow went something like this:\n# extract the APK with apktool to automatically decompile the resources and such java -jar apktool_2.2.2.jar d ctfone-490954d49dd51911bc730d8161541cf13e7416f9.apk # extract the classes.dex file from the APK for dex2jar/procyon unzip -p ctfone-490954d49dd51911bc730d8161541cf13e7416f9.apk classes.dex \u0026gt; ctfone-490954d49dd51911bc730d8161541cf13e7416f9/classes.dex # change into the directory cd ctfone-490954d49dd51911bc730d8161541cf13e7416f9/ # convert classes.dex to a jar file with dex2jar ./dex2jar-2.1-SNAPSHOT/d2j-dex2jar.sh classes.dex # use procyon to convert the jar file into a src folder java -jar procyon-decompiler-0.5.30.jar -o src -ss classes-dex2jar.jar Importing the Project # To make my life significantly simpler, I imported the decompiled app into Android Studio. The reason for this is that it would allow for a much more streamlined flow for understanding where everything was located within the application and would allow for me to re-build any missing parts of the decompiled app through reverse engineering and analysis. After importing, I needed to remove any assets that were in the decompiled version that were part of the stock android assets like layouts, animations, stock drawables, etc. Once I had removed these and beautified the code and XML, I was able to get a better grasp on what I was looking at.\nNote: This is important for the rest of my Android writeups as I use this Android Studio project throughout the rest of the Android challenges except level 5 and 6!\nSolution # The code showed that there was a tab layout being loaded and using the two fragment classes, TabFragment1 and TabFragment2 for levels 1 and 2 respectively. In TabFragment, the code will load an asset based on the name entered into the text box, R.id.Level1TextInput. If the input provided is an empty string, it will randomly load asset 1-10. This code is shown below:\n... public void loadDataFromAsset(final String s) { try { this.mImage.setImageDrawable(Drawable.createFromStream(this.getActivity().getAssets().open(s), null)); } catch (IOException ignored) {} } ... this.mButton = (Button) inflate.findViewById(R.id.lvl1button); this.mButton.setOnClickListener(new View.OnClickListener() { public void onClick(final View view) { String s; if ((s = TabFragment1.this.mInput.getText().toString()).isEmpty()) { s = \u0026#34;asset\u0026#34; + (new Random().nextInt() % 10 + 1); } TabFragment1.this.loadDataFromAsset(s); } }); ... Looking back into the Android Studio project, there are 11 assets in the assets folder:\nasset1 asset2 asset3 asset4 asset5 asset6 asset7 asset8 asset9 asset10 tHiS_iS_nOt_tHE_SeCrEt_lEveL_1_fiLE Wow, that last filename sure is odd! What shows if I enter that in? When I did, the app rendered an image containing a spongebob meme with the level 1 flag, cApwN{WELL_THAT_WAS_SUPER_EASY}\nAndroid Level 2 # Note: Please see the level 1 writeup for steps on what I did to get to this point!\nSolution # Moving on from level 1, I opened the level 2 tab. This one was very cluttered but I was able to get a better understanding for what was happening once I looked at the fragment layout and tab code. The code in TabFragment2 adds an OnClickListener to the R.id.hashmebutton to call InCryption.hashOfPlainText(). Then, it uses the result of this method as the new value for the TextView referred to by R.id.hashText.\nInCryption.hashOfPlainText() # This method deserves attention because is the only significant appearing portion of this level. The code for the method is very simple:\npublic static String hashOfPlainText() throws Exception { return getHash(new String(hex2bytes(new String(decrypt(hex2bytes(\u0026#34;0123456789ABCDEF0123456789ABCDEF\u0026#34;), hex2bytes(InCryption.encryptedHex))).trim()))); } This is made to look a lot more complicated than it is but for simplicity sake, let me pull some things out into a more human-friendly code snippet:\n// trimmed for size static String encryptedHex = \u0026#34;ec49822b5417f4dad5d6048804c07f128bb0552...\u0026#34;; ... public static String hashOfPlainText() throws Exception { // convert the super secure key and the encrypted message to byte arrays byte[] keyBytes = hex2bytes(\u0026#34;0123456789ABCDEF0123456789ABCDEF\u0026#34;); byte[] encryptedBytes = hex2bytes(encryptedHex); // decrypt the message and convert it to a string byte[] decryptedBytes = decrypt(keyBytes, encryptedBytes); String decryptedString = new String(decryptedBytes).trim(); // convert the decrypted hex string to bytes and then convert the bytes into a string String decryptedByteString = new String(hex2bytes(decryptedString)); // calculate and return the SHA256 hash of the decrypted message return getHash(decryptedByteString); } Essentially all this does is decrypt the encrypted message with the hex key 0123456789ABCDEF0123456789ABCDEF. When decrypted, the result is an ASCII hex string. This hex string is then converted into the ASCII text representation and SHA256 hashed, which is what is returned. In order to get the non-hashed value, I changed the code in the Android Studio project to return the decrypted string without hashing it:\npublic static String hashOfPlainText() throws Exception { return new String(hex2bytes(new String(decrypt(hex2bytes(\u0026#34;0123456789ABCDEF0123456789ABCDEF\u0026#34;), hex2bytes(encryptedHex))))); } This resulted in something I wasn\u0026rsquo;t really expecting which was a very long string with a multitude of DOT, DASH, and SPACE keywords. I assumed morse code and had the application print it to the console:\npublic static String hashOfPlainText() throws Exception { String res = new String(hex2bytes(new String(decrypt(hex2bytes(\u0026#34;0123456789ABCDEF0123456789ABCDEF\u0026#34;), hex2bytes(encryptedHex))))); Log.d(null, res); return res; } This gave me something much more useful:\nDASH DOT DASH DOT SPACE DOT DASH SPACE DOT DASH DASH DOT SPACE DOT DASH DASH SPACE DASH DOT SPACE DASH DOT DOT DOT SPACE DOT DASH DOT SPACE DOT DASH SPACE DASH DOT DASH DOT SPACE DASH DOT DASH SPACE DOT SPACE DASH SPACE DASH DOT DASH DOT SPACE DOT DASH DOT SPACE DASH DOT DASH DASH SPACE DOT DASH DASH DOT SPACE DASH DASH DOT DOT DOT SPACE DASH DASH DASH DASH DASH SPACE DASH DOT DOT DOT DOT SPACE DOT DASH DOT SPACE DOT DOT DOT DOT DASH SPACE DOT DASH DASH DOT SPACE DOT DOT DOT DOT SPACE DASH DOT DASH DASH SPACE DOT DOT DASH SPACE DASH DOT SPACE DASH DOT DOT SPACE DOT SPACE DOT DASH DOT SPACE DOT DOT DOT SPACE DASH DOT DASH DOT SPACE DASH DASH DASH SPACE DOT DASH DOT SPACE DOT SPACE DOT DASH DASH DASH DASH SPACE DOT DOT DOT DOT DOT SPACE DOT DOT DASH SPACE DASH DOT SPACE DASH DOT DOT SPACE DOT SPACE DOT DASH DOT SPACE DOT DOT DOT SPACE DASH DOT DASH DOT SPACE DASH DASH DASH SPACE DOT DASH DOT SPACE DOT SPACE DOT DOT DOT DOT SPACE DOT DOT DOT DOT DASH SPACE DOT DASH DOT SPACE DASH DOT DOT SPACE DOT DOT DASH SPACE DASH DOT SPACE DASH DOT DOT SPACE DOT SPACE DOT DASH DOT SPACE DOT DOT DOT SPACE DASH DOT DASH DOT SPACE DASH DASH DASH SPACE DOT DASH DOT SPACE DOT SPACE DASH DOT DOT DOT SPACE DOT DASH DOT SPACE DASH DASH DASH DASH DASH SPACE DASH DOT DOT DOT SPACE DOT DASH DOT SPACE DOT DASH SPACE DASH DOT DASH DOT SPACE DASH DOT DASH SPACE DOT SPACE DASH Now I just needed to convert the string into morse code characters, and then convert that into ASCII. Using a simple Python snippet, I converted the above string into a morse code string:\nmorseCode = \u0026#39;DASH DOT DASH DOT SPACE DOT DASH SPACE DOT DASH DASH DOT SPACE DOT DASH DASH SPACE DASH DOT SPACE DASH DOT DOT DOT SPACE DOT DASH DOT SPACE DOT DASH SPACE DASH DOT DASH DOT SPACE DASH DOT DASH SPACE DOT SPACE DASH SPACE DASH DOT DASH DOT SPACE DOT DASH DOT SPACE DASH DOT DASH DASH SPACE DOT DASH DASH DOT SPACE DASH DASH DOT DOT DOT SPACE DASH DASH DASH DASH DASH SPACE DASH DOT DOT DOT DOT SPACE DOT DASH DOT SPACE DOT DOT DOT DOT DASH SPACE DOT DASH DASH DOT SPACE DOT DOT DOT DOT SPACE DASH DOT DASH DASH SPACE DOT DOT DASH SPACE DASH DOT SPACE DASH DOT DOT SPACE DOT SPACE DOT DASH DOT SPACE DOT DOT DOT SPACE DASH DOT DASH DOT SPACE DASH DASH DASH SPACE DOT DASH DOT SPACE DOT SPACE DOT DASH DASH DASH DASH SPACE DOT DOT DOT DOT DOT SPACE DOT DOT DASH SPACE DASH DOT SPACE DASH DOT DOT SPACE DOT SPACE DOT DASH DOT SPACE DOT DOT DOT SPACE DASH DOT DASH DOT SPACE DASH DASH DASH SPACE DOT DASH DOT SPACE DOT SPACE DOT DOT DOT DOT SPACE DOT DOT DOT DOT DASH SPACE DOT DASH DOT SPACE DASH DOT DOT SPACE DOT DOT DASH SPACE DASH DOT SPACE DASH DOT DOT SPACE DOT SPACE DOT DASH DOT SPACE DOT DOT DOT SPACE DASH DOT DASH DOT SPACE DASH DASH DASH SPACE DOT DASH DOT SPACE DOT SPACE DASH DOT DOT DOT SPACE DOT DASH DOT SPACE DASH DASH DASH DASH DASH SPACE DASH DOT DOT DOT SPACE DOT DASH DOT SPACE DOT DASH SPACE DASH DOT DASH DOT SPACE DASH DOT DASH SPACE DOT SPACE DASH\u0026#39; print morseCode.replace(\u0026#39;DASH\u0026#39;, \u0026#39;-\u0026#39;).replace(\u0026#39;DOT\u0026#39;, \u0026#39;.\u0026#39;).replace(\u0026#39; \u0026#39;, \u0026#39;\u0026#39;).replace(\u0026#39;SPACE\u0026#39;, \u0026#39; \u0026#39;) The order that the character replacement was performed in was purposefully chosen due to the fact that there are spaces (\u0026quot; \u0026quot;) in the original string as well as SPACE markers. To make sure that intentional spaces weren\u0026rsquo;t removed accidentally, the \u0026quot; \u0026quot; characters were removed before replacing all instances of SPACE with \u0026quot; \u0026quot;.\nThe resulting morse code string was\n-.-. .- .--. .-- -. -... .-. .- -.-. -.- . - -.-. .-. -.-- .--. --... ----- -.... .-. ....- .--. .... -.-- ..- -. -.. . .-. ... -.-. --- .-. . .---- ..... ..- -. -.. . .-. ... -.-. --- .-. . .... ....- .-. -.. ..- -. -.. . .-. ... -.-. --- .-. . -... .-. ----- -... .-. .- -.-. -.- . - Using http://www.livephysics.com/tools/mathematical-tools/morse-code-conversion-tool/, I converted this into the string\nCAPWNBRACKETCRYP706R4PHYUNDERSCORE15UNDERSCOREH4RDUNDERSCOREBR0BRACKET Making sure to replace the written-out symbols, the resulting flag was CAPWN{CRYP706R4PHY_15_H4RD_BR0}\nAndroid Level 3 # Note: Please see the level 1 and 2 writeups for steps on what I did to get to this point!\nSolution # Okay, level 3 time. Without looking at the source code, I would imagine that this would be quite puzzling from here. However, since I had already decompiled and re-built the source code I had no problem knowing where to go. There is an activity called Level3Activity, which I could only assume was the activity to be used for Level 3. I modified my AndroidManifest.xml to use the Level3Activity class as the launch activity by changing\n... \u0026lt;activity android:label=\u0026#34;@string/app_name\u0026#34; android:name=\u0026#34;com.h1702ctf.ctfone.MainActivity\u0026#34; android:theme=\u0026#34;@style/AppTheme.NoActionBar\u0026#34;\u0026gt; ... to\n... \u0026lt;activity android:label=\u0026#34;@string/app_name\u0026#34; android:name=\u0026#34;com.h1702ctf.ctfone.Level3Activity\u0026#34; android:theme=\u0026#34;@style/AppTheme.NoActionBar\u0026#34;\u0026gt; ... and removing\n\u0026lt;activity android:exported=\u0026#34;true\u0026#34; android:name=\u0026#34;com.h1702ctf.ctfone.Level3Activity\u0026#34;/\u0026gt; Now we have a different story upon loading up the application. Alternatively, this could have been done by executing the command adb shell am start -n com.h1702ctf.ctfone/.Level3Activity and not changing the AndroidManifest.xml file. Moving on, the level 3 activity will call the method MonteCarlo.start(). This has a bunch of distracting code but the important part is the call to ArraysArraysArrays.start(). This method will just do some assorted list sort algorithms as distraction. However, the important part is that it calls ArraysArraysArrays.x(). This is a native function, meaning that it will execute a method that is in correlation to it which has been compiled from C/C++ into a shared-object (.so) library.\nI was glad to see that the creators of these challenges didn\u0026rsquo;t just jump right into this kind of challenge, as these are always a bit of a pain! So, let\u0026rsquo;s dig in. I threw the libnative-lib.so file from lib/armeabi-v7a into IDA Pro and took a look. There are lots of tools that can be used for this part but I always have more luck with IDA than I do with other disassemblers, even Binary Ninja.\nThis gave me some handy code which I will exclude here for readibility purposes. However, I rewrote it in my Android Studio project so I will put that here instead since it\u0026rsquo;s a lot nicer to read :)\n#include \u0026lt;jni.h\u0026gt; static char className[30] = {0}; static char classNameEnc[29] = { 0x5E, 0x52, 0x50, 0x12, 0x55, 0x0C, 0x0A, 0x0D, 0x0F, 0x5E, 0x49, 0x5B, 0x12, 0x5E, 0x49, 0x5B, 0x52, 0x53, 0x58, 0x12, 0x6F, 0x58, 0x4C, 0x48, 0x58, 0x4E, 0x49, 0x52, 0x4F }; static char methodName[8] = {0}; static char methodNameEnc[7] = {\u0026#39;^\u0026#39;, \u0026#39;I\u0026#39;, \u0026#39;]\u0026#39;, \u0026#39;Y\u0026#39;, \u0026#39;I\u0026#39;, \u0026#39;_\u0026#39;, \u0026#39;X\u0026#39;}; static char signature[4] = {0}; static char signatureEnc[3] = {0x70, 0x71, 0x0E}; ... JNIEXPORT void JNICALL Java_com_h1702ctf_ctfone_ArraysArraysArrays_x(JNIEnv* env, jobject thiz) { if(!className[0]) { for(int i = 0; i \u0026lt; 29; i++) { className[i] = (char) (classNameEnc[i] ^ 0x3D); } } jclass clazz = (*env)-\u0026gt;FindClass(env, className); if(!methodName[0]) { for(int i = 0; i \u0026lt; 7; i++) { methodName[i] = (char) (methodNameEnc[i] ^ 0x2C); } } if(!signature[0]) { for(int i = 0; i \u0026lt; 3; i++) { signature[i] = (char) (signatureEnc[i] ^ 0x58); } } jmethodID methodID = (*env)-\u0026gt;GetStaticMethodID(env, clazz, methodName, signature); (*env)-\u0026gt;CallStaticVoidMethod(env, clazz, methodID); } Essentially, what this does is perform a bunch of simple string XORs, and then use those to call the static method Requestor.request(). This method will build an OkHttpClient with three different SSL certificates pinned, meaning that if you attempted to proxy the device through a web proxy that was re-signing the SSL traffic, you would still not be able to see it because the device is comparing the certificate in the response to the ones which have been set as valid ones. It then adds a header to the request using the methods Requestor.hName() and Requestor.hVal() for the header name and value respectively.\nOf course, these are also native methods!\nSimilarly to the ArraysArraysArrays.x() method, the strings are just obfuscated with an XOR. I also re-wrote these in my project and they can be seen below:\n#include \u0026lt;jni.h\u0026gt; static char hName[14] = {0}; static char hNameEnc[13] = { 0x6f, 0x1a, 0x7b, 0x52, 0x41, 0x52, 0x5b, 0x04, 0x1a, 0x71, 0x5b, 0x56, 0x50 }; static char hVal[73] = {0}; static char hValEnc[72] = { 0x68, 0x0F, 0x6C, 0x7D, 0x6C, 0x0C, 0x6F, 0x47, 0x6B, 0x66, 0x5A, 0x71, 0x68, 0x79, 0x6C, 0x71, 0x68, 0x53, 0x4E, 0x50, 0x5A, 0x0F, 0x52, 0x4D, 0x69, 0x6A, 0x68, 0x55, 0x68, 0x0F, 0x74, 0x67, 0x6A, 0x68, 0x5A, 0x4D, 0x6A, 0x55, 0x0E, 0x49, 0x5D, 0x79, 0x0F, 0x6B, 0x5F, 0x55, 0x4E, 0x48, 0x64, 0x68, 0x6B, 0x46, 0x70, 0x52, 0x6C, 0x4F, 0x5C, 0x7B, 0x6C, 0x5F, 0x5B, 0x54, 0x7F, 0x0B, 0x6F, 0x0C, 0x5D, 0x07, 0x6E, 0x6F, 0x51, 0x03 }; ... JNIEXPORT jstring JNICALL Java_com_h1702ctf_ctfone_Requestor_hName(JNIEnv* env, jobject thiz) { if(!hName[0]) { for(int i = 0; i \u0026lt; 13; i++) { hName[i] = (char) (hNameEnc[i] ^ 0x37); } } return (*env)-\u0026gt;NewStringUTF(env, hName); } JNIEXPORT jstring JNICALL Java_com_h1702ctf_ctfone_Requestor_hVal(JNIEnv* env, jobject thiz) { if(!hVal[0]) { for(int i = 0; i \u0026lt; 72; i++) { hVal[i] = (char) (hValEnc[i] ^ 0x3E); } } return (*env)-\u0026gt;NewStringUTF(env, hVal); } ... Calling Requestor.hName() will result in the string X-Level3-Flag and calling Request.hVal() will result in the string V1RCR2QyUXdOVGROVmpnd1lsWTVkV1JYTVdsTk0wcG1UakpvZVUxNlRqbERaejA5Q2c9PQo=. Base64 decoding this value will give WTBGd2QwNTdNVjgwYlY5dWRXMWlNM0pmTjJoeU16TjlDZz09Cg==. Base64 decoding that value will give Y0Fwd057MV80bV9udW1iM3JfN2hyMzN9Cg==. One more time! Base64 decoding that value will give the flag, cApwN{1_4m_numb3r_7hr33}\nAndroid Level 4 # Note: Please see the level 1, 2, and 3 writeups for steps on what I did to get to this point!\nSolution # So\u0026hellip;.this had me pondering for a while what was going on and what I was missing. I was pretty sure that it had to do with a mysterious uncalled method, MonteCarlo.functionnameLeftbraceOneCommaTwoCommaThreeCommaRightbraceFour(p0, p1, p2) but I had no idea what to use as arguments. Then, I read through the challenge hint again and realized it! The hint says Hope you kept your notes. and the function takes 3 strings\u0026hellip;the IDA Pro decompilation was less helpful for this function and all I really extrapolated out of it was that it was converting the three arguments into char* using GetStringUTFChars and doing some xor function that resides within libsodium \u0026ndash; a crypto library\u0026hellip;I\u0026rsquo;m not good at crypto. However, that is okay because I didn\u0026rsquo;t need to be! Using frida, I attached to the running application and used this snippet to call the function mentioned above, providing it with the three previous flags as the arguments:\nJava.perform(function() { var MonteCarlo = Java.use(\u0026#39;com.h1702ctf.ctfone.MonteCarlo\u0026#39;); var mc = MonteCarlo.$new(); console.log(mc.functionnameLeftbraceOneCommaTwoCommaThreeCommaRightbraceFour(\u0026#39;cApwN{WELL_THAT_WAS_SUPER_EASY}\u0026#39;, \u0026#39;CAPWN{CRYP706R4PHY_15_H4RD_BR0}\u0026#39;, \u0026#39;cApwN{1_4m_numb3r_7hr33}\u0026#39;)); }) This resulted in the flag being printed to the console, cApwN{w1nn3r_w1nn3r_ch1ck3n_d1nn3r!}.\nAndroid Level 5 # Note: Please see the level 1, 2, 3, and 4 writeups for steps on what I did to get to this point! I used the same methods to decompile and re-building the Android Studio project as I did in levels 1-4.\nSolution # The level 5 application given was a bit more complex than the one used for levels 1-4. This one contained a service, called CruelIntentions which contained the majority of the interesting code. The MainActivity had a function, called flag which would take three inputs and give a string output and the assembly revealed that it did the same thing as functionnameLeftbraceOneCommaTwoCommaThreeCommaRightbraceFour from the previous level.\nWhen running the app on a phone, there was a screen with three different input boxes, a submit button, and a small button at the bottom that would pop up a hint in the Snackbar saying State the secret phrase (omit the oh ex). \u0026ldquo;Oh Ex\u0026rdquo; would imply a 0x, meaning that these strings are probably hex strings.\nFocusing on the CruelIntentions service, there was a function called startActionHint(Context context, String s). This would create an intent to start the CruelIntentions service with an extra string parameter, com.h1702ctf.ctfone5.extra.PARAM1, containing whatever value that was provided into the second parameter, s. Then, in the onHandleIntent function which is called when the intent is received, will if the extra string parameter is provided. If it is, it calls handleActionHint(String s) with the string contained in the intent extra. This function will then verify if the string is equal to \u0026quot;orange\u0026quot; and if it is, it will call the native library function, CruelIntentions.one().\nThis is where things get interesting. So I opened the native library into IDA Pro and took a look at the function. If you try to solve this challenge simply by using the pseudocode, you will have a lot of trouble and I would even go as far as to say that it is impossible to do so. The function will first check the /proc/PID/status file (where PID is the process ID of the app) to check if a debugger is attached. If one is not, it then will check to see if there are su binaries at various paths. Then it will do some checks on a bunch of hardcoded strings, of which most are palindromes. Then, it will check for the system property mobsec.setme to see if it has a value of 1. If it does, the app will check for if a debugger is attached again, and exit. All of this is meant to just distract away from what is actually going on. After getting farily stumped by the pseudocode, I took a look at the assembly itself to see if there was perhaps a return value being that I was missing but would never truely be returned to the Java code as a result of the function return type being void. This is when I noticed that at the end of the function if the mobsec.setme system property was set to 1, it would put some strange values into r0, r1, r2, and lr. In ARM assembly, r0, r1, and r2 are used for the first three parameters to a function when being called. The values being used to set these registers were large hex number that seemed rather strange. So, I did the math myself and ended up with the following values in the registers:\nr0 = 0xbea7ab1e r1 = 0xface1e55 r2 = 0xda7aba5e lr = 0xdeadbabe These are all semi-mnemonic hex strings that resemble english words but using only valid hexadecimal characters. Now, this is the point where I was very confused. I tried using these three strings in r0, r1, and r2 as the values to put into the MainActivity.flag() function (without the 0x\u0026rsquo;s) but it was returning gibberish back. Then, after many days of reverse engineering and attempting to figure out the proper solution that I was not seeing and continually going through the CruelIntentions.one() function, I tried the same values again on the phone and sure enough, got the flag!?! Gah.\nAnyways, the flag was cApwN{sPEaK_FrieNd_aNd_enteR!}\niOS Writeups Foreword # These iOS writeups will be far less detailed and verbose than the Android ones because I forgot to write them up and had to stuff all the writing into 3 hours before the CTF ended. Sorry!\niOS Level 1 # Solution # First, I installed the IPA file onto my Jailbroken iOS 9.3.3 device using App Installer, a cydia package that allows for installation of IPA files from the terminal. To connect to my device, I utilize an application on my macOS machine called iPhoneTunnel which I believe can be found on a mirror here. I had this tool on my computer already from previous work so finding it again is up to you, but there are other methods.\nAnother method I have used in the past that I have found to be effective is using multcprelay which is a fork of a popular tool called tcprelay.py, which will allow you to SSH over USB by tunneling using usbmuxd. The difference between tcprelay and multcprelay is that it can work with multiple devices plugged in. multcprelay can be found here, which has some fixes pulled into it that I have made myself.\nAnyways, I installed the app on my device and launched it to find a tabbed app with a tab for each level. So, I extracted the IPA file, pulled out the actual binary portion from the .app, and opened it with Hopper. Using hopper I started looking for various strings that may indicate a flag, only to find that one of the strings was \u0026quot;Level 1: The flag isn't in the code!\u0026quot;. Well that\u0026rsquo;s handy. Going back into the .app folder, I noticed an Assets.car file. A quick google search revealed that this can be decompiled using Asset Catalog Tinkerer. Using this, I opened the .car file and found an image in there containing the first flag, cApwN{y0u_are_th3_ch0sen_1}\niOS Level 2 # Solution # I ended up having a lot of trouble getting the information I needed using Hopper, so I transitioned over to using IDA Pro instead. Opening the app binary in IDA, I searched for the functions relating to level 2, like -[IntroLevels.Level2ViewController \u0026lt;function name\u0026gt;] (obviously where \u0026lt;function name\u0026gt; is a function name). There was a button to press on the Level 2 tab view that would do some actions on a string that was entered into a text box and output a string. So, I investigated what the button was doing.\nThe -[IntroLevels.Level2ViewController buttonTouched:] function was doing some math and hashing on the input string and I noticed some kind of comparison happening to a string, 5b6da8f65476a399050c501e27ab7d91. Googling this told me that this was the hash for 424241. So, I tried entering that as the string and sure enough got the flag, cApwN{0mg_d0es_h3_pr4y}\niOS Level 3 # Solution # This level had a rock, paper, scissors, spock game where you would pick one and see if you could beat the computer. I tried to beat the computer manually a couple times before realizing something\u0026hellip;it was only picking spock! However, when I tried to pick spock myself, it would say I picked it too slowly and that I was being reported.\nSo how would it report me exactly? I assumed some kind of web request, so I proxied my iOS device through Charles Proxy and saw when I picked spock, it would send a request to google.com with an extra header containing the flag.\nThe header was look at me i am a header: cApwN{1m_1n_ur_n00twork_tere3fik}, obviously giving us the flag.\niOS Level 4 # Solution # Similar to the Android challenges, there was a function in the app binary called +[ZhuLi doTheThing:flag2:flag3:]. Using Frida, I called the function and provided it with my flags like so:\nObjC.classes.ZhuLi.doTheThing_flag2_flag3_(\u0026#39;cApwN{y0u_are_th3_ch0sen_1}\u0026#39;, \u0026#39;cApwN{0mg_d0es_h3_pr4y}\u0026#39;, \u0026#39;cApwN{1m_1n_ur_n00twork_tere3fik}\u0026#39;).toString() This resulted in a hex string, 634170774e7b6630685f7377317a7a6c655f6d795f6e317a7a6c657d.\nVefore trying to mitigate or bypass any encryption, I looked at the function in IDA Pro and saw that it was simply converting the result into hex, nothing fancy. A quick python one-liner gave me the flag:\nIn [1]: import binascii In [2]: binascii.unhexlify(\u0026#39;634170774e7b6630685f7377317a7a6c655f6d795f6e317a7a6c657d\u0026#39;) Out[2]: \u0026#39;cApwN{f0h_sw1zzle_my_n1zzle}\u0026#39; iOS Level 5 # Solution # This was a strange looking application with a button that said \u0026quot;Hammer time!\u0026quot;. Pressing this button would cause the application to crash, so I figured a deeper analysis was needed as there was no Assets.car or anything this time, just a normal iOS app. Opening the app binary in IDA Pro, I took a look at the onButtonPress: code and saw that there was a check being performed using the KeychainThing class in the application. The check would see if a keychain entry existed with the identifier \u0026quot;setmeinurkeychain\u0026quot; containing the value \u0026quot;youdidathing\u0026quot; using the -[KeychainThing searchKeychainCopyMatching:] method. So, using Frida I made a new instance of the KeychainThing class and used it to add the entry in using the -[KeychainThing createKeychainValue:forIdentifier:] method:\nkc = ObjC.classes.KeychainThing.alloc().init() kc.createKeychainValue_forIdentifier_(\u0026#39;youdidathing\u0026#39;, \u0026#39;setmeinurkeychain\u0026#39;) After this, I was able to press the button without the app crashing and a new view appeared with a pixel-art flag spelled out in vertical order, cApwN{i_guess_you_can_touch_this}\nConclusion # And that\u0026rsquo;s it! If you have any questions, feel free to hit me up on Twitter or somewhere else if you feel so inclined.\n~Joel\n","date":"16 July 2017","externalUrl":null,"permalink":"/posts/h1702ctf/","section":"Posts","summary":"","title":"H1702 CTF","type":"post"},{"content":"","date":"16 July 2017","externalUrl":null,"permalink":"/categories/writeups/","section":"Categories","summary":"","title":"Writeups","type":"categories"},{"content":"This was the third reversing challenge in TrendMicro CTF 2017. You can download it here (md5: 38256f02d260bca145e90e06e474175a).\nTo extract it, as per the same instructions as the original challenge:\n$ openssl enc -d -aes-256-cbc -k aOw0ZxbUtoj9BdHYtxjn -in files15.enc -out files15.zip # or if you have problems with the first command $ openssl enc -d -aes-256-cbc -k aOw0ZxbUtoj9BdHYtxjn -in files15.enc -out files15.zip -md md5 # then, $ unzip files15.zip Unfortunately the challenge is now shut down, so I cannot quite remember the original description or hint but I do remember it had to do with Powershell. If I find it posted somewhere else later, I\u0026rsquo;ll update this!\nAnyway, we are given a single file \u0026mdash; G0AL.BAT, containing:\n@echo off set j=Thank_You_For_Joining_TMCTF2017 set k=Tested on Win7SP1 32-bit OS set l=2eub2XQk9DHSsncxyWSLcTCLdgyLdhyLRgiLfiCLNjhPGHXzWQHR/+Fgi2wkJItFPItUKHgB6otKGItaIAHr4zRJizSLAe4x/zHA/KyEwHQHwc8NAcfr9Dt8JCh14YtaJAHrZosMS4taHAHriwSLAeiJRCQcYcOyCCnUieWJwmiOTg7sUuif////iUUEu37Y4nOHHCRS6I7///+ set m=moAsSAMOY%02maCkAOwBsdf%smadfdf9z///+qamogG8AZABSDGsaSSwhmzWMOYsA+/masdgmoKYqWTAGEAaQBuAC4ARAB//IAbA== set n=LABNAmaodSDGASJOIHGI76msdm%ls:1qwerATSAYUDBGOSSnsAMIOLM//sogs+AuAFasgqQQYYHAFAZ2%:~QBtAGIAbABdABEA+ set o=JRQhobGwgQWgzMi5kaHVzZXIw24hcJAqJ5lb/VQSJwlC7qKJNvIccJFLoX////2hyb1ggaGRNaWNoVHJlbjHbiFwkConjaCF9WCBoZ2FpbmhzTWVBaCFJdEloaGVyZWhsbG9UaEZ7SGVoVE1DVDHJiEwkHonhMdJSU1FS/9AxwFD/VQg= set p=GUAZQBwACgAOQAwACkAOwBGOALAHIAeQB7AGYAdQBuAGMAdABpAG8AbgAgAGcAZABlAGwAZQBnAGEAdABlAHsAUABhAHIAYQBtACAAKABbAFAAYQByAGEAbQBlAHQAZQByACgAUABvAHMAaQBGOALAGkAbwBuADGOALAMAAsAEGOALAYQBuAGQAYQBGOALAG8AcgB5ADGOALAJABUAHIAdQBlACkAXQAgAFsAVAB5AHAAZQBbAFGOALAXQAgACQAUABhAHIAYQBtAGUAdABlAHIAcwAsAFsAUABhAHIAYQBtAGUAdABlAHIAKABQAG8AcwBpAHQAaQBvAG4APQAxACkAXQAgAFsAVAB5AHAAZQBdACAAJABSAGUAdAB1AHIAbgBUAHkAcABlADGOALAWwBWAG8AaQBkAFGOALAKQA7ACQAVAB5AHAAZQBCAHUAaQBsAGQAZQByADGOALAWwBBAHAAcABEAG8AbQBhAGkAbgBdADoAOgBDAHUAcgByAGUAbgBGOALAEQAbwBtAGEAaQBuAC4ARABlAGYAaQBuAGUARAB5AG4AYQBtAGkAYwBBAHMAcwBlAGGOALAYgBsAHkAKAAoAE4AZQB3ACGOALATwBiAGoAZQBjAHQAIABTAHkAcwBGOALAGUAbQAuAFIAZQBmAGwAZQBjAHQAaQBvAG4ALgBBAHMAcwBlAGGOALAYgBsAHkATgBhAGGOALAZQAoACIAUgBlAGYAbABlAGMAdABlAGQARABlAGwAZQBnAGEAdABlACIAKQApACwAWwBTAHkAcwBGOALAGUAbQAuAFIAZQBmAGwAZQBjAHQAaQBvAG4ALgBFAGGOALAaQBGOALAC4AQQBzAHMAZQBtAGIAbAB5AEIAdQBpAGwAZABlAHIAQQBjAGMAZQBzAHMAXQA6ADoAUgB1AG4AKQAuAEQAZQBmAGkAbgBlAEQAeQBuAGEAbQBpAGMATQBvAGQAdQBsAGUAKAAiAEkAbgBNAGUAbQBvAHIAeQBNAG8AZAB1AGwAZQAiACwAJABmAGEAbABzAGUAKQAuAEQAZQBmAGkAbgBlAFQAeQBwAGUAKAAiAFgAWABYACIALAAiAEMAbABhAHMAcwAsAFAAdQBiAGwAaQBjACwAUwBlAGEAbABlAGQALABBAG4Acw^BpAEMAbABhAHMAcwAsAEEAdQBGOALAG8AQwBsAGEAcwBzACIALABbAFMAeQBzAHQAZQBtAC4ATQB1AGwAdABpAGMAYQBzAHQARABlAGwAZQBnAGEAdABlAFGOALAKQA7ACQAVAB5AHAAZQBCAHUAaQBsAGQAZQByAC4ARABlAGYAaQBuAGUAQwBvAG4AcwBGOALAHIAdQBjAHQAbwByACgAIgBSAFQAUwBwAGUAYwBpAGEAbABOAGEAbQBlACwASABpAGQAZQBCAHkAUwBpAGcALABQAHUAYgBsAGkAYwAiACwAWwBTAHkAcwBGOALAGUAbQAuAFIAZQBmAGwAZQBjAHQAaQBvAG4ALgBDAGEAbABsAGkAbgBnAEMAbwBuAHYAZQBuAHQAaQBvAG4AcwBdADoAOgBTAHQAYQBuAGQAYQByAGQALAAkAFAAYQByAGEAbQBlAHQAZQByAHMAKQAuAFMAZQBGOALAEkAbQBwAGwAZQBtAGUAbgBGOALAGEAdABpAG8AbgBGAGwAYQBnAHMAKAAiAFIAdQBuAHQAaQBtAGUALABNAGEAbgBhAGcAZQBkACIAKQA7ACQAVAB5AHAAZQBCAHUAaQBsAGQAZQByAC4ARABlAGYAaQBuAGUATQBlAHQAaABvAGQAKAAiAEkAbgB2AG8AawBlACIALAAiAFAAdQBiAGwAaQBjACwASABpAGQAZQBCAHkAUwBpAGcALABOAGUAdwBTAGwAbwBGOALACwAVgBpAHIAdAB1AGEAbAAiACwAJABSAGUAdAB1AHIAbgBUAHkAcABlACwAJABQAGEAcgBhAGGOALAZQBGOALAGUAcgBzACkALgBTAGUAdABJAGGOALAcABsAGUAbQBlAG4AdABhAHQAaQBvAG4ARgBsAGEAZwBzACgAIgBSAHUAbgBGOALAGkAbQBlACwATQBhAG4AYQBnAGUAZAAiACkAOwByAGUAdAB1AHIAbgAgACQAVAB5AHAAZQBCAHUAaQBsAGQAZQByAC4AQwByAGUAYQBGOALAGUAVAB5AHAAZQAoACkAOwB9AGYAdQBuAGMAdABpAG8AbgAgAGcAcAByAG8AYwB7AFAAYQByAGEAbQAgACgAWwBQAGEAcgBhAGGOALAZQBGOALAGUAcgAoAFAAbwBzAGkAdABpAG8AbgA9ADAALABNAGEAbgBkAGEAdABvAHIAeQA9ACQAVAByAHUAZQApAFGOALAIABbAFMAdAByAGkAbgBnAFGOALAIAAkAEGOALAbwBkAHUAbABlACwAWwBQAGEAcgBhAGGOALAZQBGOALAGUAcgAoAFAAbwBzAGkAdABpAG8AbgA9ADEALABNAGEAbgBkAGEAdABvAHIAeQA9ACQAVAByAHUAZQApAFGOALAIABbAFMAdAByAGkAbgBnAFGOALAIAAkAFAAcgBvAGMAZQBkAHUAcgBlACkAOwAkAFMAeQBzAHQAZQBtAEEAcwBzAGUAbQBiAGwAeQA9AFsAQQBwAHAARABvAGGOALAYQBpAG4AXQA6ADoAQwB1AHIAcgBlAG4AdABEAG8AbQBhAGkAbgAuAEcAZQBGOALAEEAcwBzAGUAbQBiAGwAaQBlAHMAKAApAHwAVwBoAGUAcgBlACGOALATwBiAGoAZQBjAHQAewAkAF8ALgBHAGwAbwBiAGEAbABBAHMAcwBlAGGOALAYgBsAHkAQwBhAGMAaABlACAALQBBAG4AZAAgACQAXwAuAEwAbwBjAGEAdABpAG8AbgAuAFMAcABsAGkAdAAoACIAXAAiACkAWwAtADEAXQAuAEUAcQB1AGEAbABzACgAIgBTAHkAcwBGOALAGUAbQAuAGQAbABsACIAKQB9ADsAJABVAG4AcwBhAGYAZQBOAGEAdABpAHYAZQBNAGUAdABoAG8AZABzADGOALAJABTAHkAcwBGOALAGUAbQBBAHMAcwBlAGGOALAYgBsAHkALgBHAGUAdABUAHkAcABlACgAIgBNAGkAYwByAG8AcwBvAGYAdAAuAFcAaQBuADMAMgAuAFUAbgBzAGEAZgBlAE4AYQBGOALAGkAdgBlAEGOALAZQBGOALAGgAbwBkAHMAIgApADsAcgBlAHQAdQByAG4AIAAkAFUAbgBzAGEAZgBlAE4AYQBGOALAGkAdgBlAEGOALAZQBGOALAGgAbwBkAHMALgBHAGUAdABNAGUAdABoAG8AZAAoACIARwBlAHQAUAByAG8AYwBBAGQAZAByAGUAcwBzACIAKQAuAEkAbgB2AG8AawBlACgAJABuAHUAbABsACwAQAAoAFsAUwB5AHMAdABlA%0gBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBIAGEAbgBkAGwAZQBSAGUAZgBdACgATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4AUgB1AG4AdABpAGGOALAZQAuAEkAbgBGOALAGUAcgBvAHAAUwBlAHIAdgBpAGMAZQBzAC4ASABhAG4AZABsAGUAUgBlAGYAKAAoAE4AZQB3ACGOALATwBiAGoAZQBjAHQAIABJAG4AdABQAHQAcgApACwAJABVAG4AcwBhAGYAZQBOAGEAdABpAHYAZQBNAGUAdABoAG8AZABzAC4ARwBlAHQATQBlAHQAaABvAGQAKAAiAEcAZQBGOALAEGOALAbwBkAHUAbABlAEgAYQBuAGQAbABlACIAKQAuAEkAbgB2AG8AawBlACgAJABuAHUAbABsACwAQAAoACQATQBvAGQAdQBsAGUAKQApACkAKQAsACQAUAByAG8AYwBlAGQAdQByAGUAKQApADsAfQBbAEIAeQBGOALAGUAWwBdAFGOALAJABzAGMAMwAyACAAPQAgAFsAUwB5AHMAdABlAGGOALALgBDAG8AbgB2AGUAcgBGOALAFGOALAOgA6AEYAcgBvAGGOALAQgBhAHMAZQA2ADQAUwBGOALAHIAaQBuAGcAKAAkAGUAbgB2ADoAbAArACQAZQBuAHYAOgBPACkAOwAkAGEAPQBHAGUAdAAtAEQAYQBGOALAGUAOwBpAGYAKAAkAGEALgBNAG8AbgBGOALAGgAIAAtAGcAZQAgADIAKQB7AGUAeABpAHQAOwB9AFsAVQBpAG4AdAAzADIAWwBdAFGOALAIAAkAG8AcAA9ADAAOwAkAHIAPQAoAFsAUwB5AHMAdABlAGGOALALgBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBNAGEAcgBzAGgAYQBsAFGOALAOgA6AEcAZQBGOALAEQAZQBsAGUAZwBhAHQAZQBGAG8AcgBGAHUAbgBjAHQAaQBvAG4AUABvAGkAbgBGOALAGUAcgAoACgAZwBwAHIAbwBjACAAawBlAHIAbgBlAGwAMwAyAC4AZABsAGwAIABWAGkAcgBGOALAHUAYQBsAFAAcgBvAHQAZQBjAHQAKQAsACgAZwBkAGUAbABlAGcAYQBGOALAGUAIABAACgAWwBCAHkAdABlAFsAXQBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBVAEkAbgBGOALADMAMgBbAFGOALAXQApACAAKABbAEkAbgBGOALAFAAdAByAFGOALAKQApACkAKQAuAEkAbgB2AG8AawBlACgAJABzAGMAMwAyACwAJABzAGMAMwAyAC4ATABlAG4AZwBGOALAGgALAAwAHgANAAwACwAJABvAHAAKQA7AGkAZgAoACQAcgAgACGOALAZQBxACAAMAApAHsAJABwAHIAPQAoAFsAUwB5AHMAdABlAGGOALALgBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBNAGEAcgBzAGgAYQBsAFGOALAOgA6AEcAZQBGOALAEQAZQBsAGUAZwBhAHQAZQBGAG8AcgBGAHUAbgBjAHQAaQBvAG4AUABvAGkAbgBGOALAGUAcgAoACgAZwBwAHIAbwBjACAAawBlAHIAbgBlAGwAMwAyAC4AZABsAGwAIABWAGkAcgBGOALAHUAYQBsAEEAbABsAG8AYwApACwAKABnAGQAZQBsAGUAZwBhAHQAZQAgAEAAKABbAEkAbgBGOALAFAAdAByAFGOALALABbAFUASQBuAHQAMwAyAFGOALALABbAFUASQBuAHQAMwAyAFGOALALABbAFUASQBuAHQAMwAyAFGOALAKQAgACgAWwBVAEkAbgBGOALADMAMgBdACkAKQApACkALgBJAG4AdgBvAGsAZQAoADAALAAkAHMAYwAzADIALgBMAGUAbgBnAHQAaAAsADAAeAAzADAAMAAwACwAMAB4ADQAMAApADsAaQBmACgAJABwAHIAIAAtAG4AZQAgADAAKQB7ACQAbQBlAGGOALAcwBlAHQAPQAoAFsAUwB5AHMAdABlAGGOALALgBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBNAGEAcgBzAGgAYQ^BsAFGOALAOgA6AEcAZQBGOALAEQAZQBsAGUAZwBhAHQAZQBGAG8AcgBGAHUAbgBjAHQAaQBvAG4AUABvAGkAbgBGOALAGUAcgAoACgAZwBwAHIAbwBjACAAbQBzAHYAYwByAHQALgBkAGwAbAAgAGGOALAZQBtAHMAZQBGOALACkALAAoAGcAZABlAGwAZQBnAGEAdABlACAAQAAoAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQApACAAKABbAEkAbgBGOALAFAAdAByAFGOALAKQApACkAKQA7AGYAbwByACAAKAAkAGkAPQAwADsAJABpACAALQBsAGUAIAAoACQAcwBjADMAMgAuAEwAZQBuAGcAdABoACGOALAMQApADsAJABpACsAKwApACAAewAkAGGOALAZQBtAHMAZQBGOALAC4ASQBuAHYAbwBrAGUAKAAoACQAcAByACsAJABpACkALAAgACQAcwBjADMAMgBbACQAaQBdACwAIAAxACkAfQA7ACgAWwBTAHkAcwBGOALAGUAbQAuAFIAdQBuAHQAaQBtAGUALgBJAG4AdABlAHIAbwBwAFMAZQByAHYAaQBjAGUAcwAuAEGOALAYQByAHMAaABhAGwAXQA6ADoARwBlAHQARABlAGwAZQBnAGEAdABlAEYAbwByAEYAdQBuAGMAdABpAG8AbgBQAG8AaQBuAHQAZQByACgAKABnAHAAcgBvAGMAIABrAGUAcgBuAGUAbAAzADIALgBkAGwAbAAgAEMAcgBlAGEAdABlAFQAaAByAGUAYQBkACkALAAoAGcAZABlAGwAZQBnAGEAdABlACAAQAAoAFsASQBuAHQAUABGOALAHIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsASQBuAHQAUABGOALAHIAXQApACAAKABbAEkAbgBGOALAFAAdAByAFGOALAKQApACkAKQAuAEkAbgB2AG8AawBlACgAMAAsADAALAAkAHAAcgAsACQAcAByACwAMAAsADAAKQA7AHGOALAfQBlAGwAcwBlAHsAKABbAFMAeQBzAHQAZQBtAC4AUgB1AG4AdABpAGGOALAZQAuAEkAbgBGOALAGUAcgBvAHAAUwBlAHIAdgBpAGMAZQBzAC4ATQBhAHIAcwBoAGEAbABdADoAOgBHAGUAdABEAGUAbABlAGcAYQBGOALAGUARgBvAHIARgB1AG4AYwBGOALAGkAbwBuAFAAbwBpAG4AdABlAHIAKAAoAGcAcAByAG8AYwAgAGsAZQByAG4AZQBsADMAMgAuAGQAbABsACAAQwByAGUAYQBGOALAGUAVABoAHIAZQBhAGQAKQAsACgAZwBkAGUAbABlAGcAYQBGOALAGUAIABAACgAWwBJAG4AdABQAHQAcgBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBCAHkAdABlAFsAXQBdACwAWwBCAHkAdABlAFsAXQBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBJAG4AdABQAHQAcgBdACkAIAAoAFsASQBuAHQAUABGOALAHIAXQApACkAKQApAC4ASQBuAHYAbwBrAGUAKAAwACwAMAAsACQAcwBjADMAMgAsACQAcwBjADMAMgAsADAALAAwACkAOwB9AHMAbABlAGUAcAAoADEAMgAwADAAKQA7AHGOALAYwBhAHQAYwBoAHsAfQBlAHgAaQBGOALADsA\u0026#34; cmd /c \u0026#34;powershell -command \u0026#34;$a=get-date;$t=[system.Text.Encoding]::UTF8.GetBytes(\u0026#39;fzEvD\u0026#39;);for ($i=0;$i -le ($t.Length-1);$i++){$t[$i]=$t[$i]-$a.hour} $d=[system.Text.Encoding]::UTF8.GetString($t);[Environment]::SetEnvironmentVariable(\u0026#39;q\u0026#39;, $d, \u0026#39;User\u0026#39;);\u0026#34; cmd /c \u0026#34;powershell -enc %q%%p:GOAL=0% \u0026gt; NUL echo %j% echo %q% set j= set k= set l= set m= set n= set o= set p= The first thing that is important to realize is that this is a batch file, not a powershell file. Because of this, we must analyze what is going on first as a batch file (even though there is Powershell code executed inside of it).\nFirst, a bunch of variables are being set to things that kinda look like incomplete base64. Also, the variable p is being set with a long string and has a weird straggling \u0026quot; at the end \u0026mdash; this will make sense later. Next, a powershell script is run that has the following condensed into one line:\n$date = Get-Date; $str = [System.Text.Encoding]::UTF8.GetBytes(\u0026#39;fzEvD\u0026#39;); for($i = 0; $i -le ($str.Length - 1); $i++) { $str[$i] = $str[$i] - $date.hour; } $shiftedStr = [System.Text.Encoding]::UTF8.GetString($t); [Environment]::SetEnvironmentVariable(\u0026#39;q\u0026#39;, $shiftedStr, \u0026#39;User\u0026#39;); This script will take the string fzEvD and shift the characters down by whatever hour it is currently \u0026mdash; 0 to 23. It then takes this new string, and sets it to the user environment variable q. Then, the batch script performs some string transformations and executes a base64-encoded powershell script.\nExamining the string transformations,\ncmd /c \u0026#34;powershell -enc %q%%p:GOAL=0% \u0026gt; NUL This command only has a \u0026quot; at the beginning because it is closed off by the \u0026quot; at the end of p \u0026mdash; this is what I was referring to earlier. Next, %q% is simply going to insert the value of q which was set by the powershell script. After this, %p:GOAL=0% is going to insert the value of p but it is going to replace every instance of the string GOAL with 0 \u0026mdash; something seemed very repetitive in that \u0026ldquo;base64\u0026rdquo;! Lastly, the \u0026quot; finishes off the script and is all piped into NUL, making the output hidden if it fails! This is deceptive because there is an echo at the beginning saying Thank_You_For_Joining_TMCTF2017, making you think you just got the flag! BAMBOOZLERS! Now, we need to figure out what that base64 encoded script is doing. To do that, we need to figure out what hour of the day this was intended to be run at to get the correct beginning portion of the base64 script. I put together a small python script that would do this for me by checking every possible hour and performing the shift. Then it would base64 decode the entire value with the shifted string at the front and print it out \u0026mdash; if it would even decode at all.\nHere was my script:\n# the base \u0026#34;p\u0026#34; that we have with all \u0026#34;GOAL\u0026#34; replaced with \u0026#34;0\u0026#34; --- trimmed for readability base = \u0026#39;GUAZQBwACgAOQAwACkAOwB0AH...\u0026#39; # each hour value possible for k in xrange(24): # shift all the characters in the front portion front = \u0026#39;\u0026#39;.join(chr(ord(c) - k) for c in \u0026#39;fzEvD\u0026#39;) try: # insert it into the string and remove all these random # NUL bytes I discovered after debugging what was being printed out out = base64.b64decode(\u0026#39;{}{}\u0026#39;.format(front, base)).replace(\u0026#39;\\x00\u0026#39;, \u0026#39;\u0026#39;) # we want it to spell out the start of the code being \u0026#34;sleep(90);\u0026#34; # this will make sure we have it de-mangled if out.startswith(\u0026#39;sleep\u0026#39;): print k except: pass After running this, we find that the k hour value we want is 3! Once we base64-decoded the string and stripped it of NUL bytes, we get this script:\nsleep(90);try{function gdelegate{Param ([Parameter(Position=0,Mandatory=$True)] [Type[]] $Parameters,[Parameter(Position=1)] [Type] $ReturnType=[Void]);$TypeBuilder=[AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName(\u0026#34;ReflectedDelegate\u0026#34;)),[System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule(\u0026#34;InMemoryModule\u0026#34;,$false).DefineType(\u0026#34;XXX\u0026#34;,\u0026#34;Class,Public,Sealed,AnsiClass,AutoClass\u0026#34;,[System.MulticastDelegate]);$TypeBuilder.DefineConstructor(\u0026#34;RTSpecialName,HideBySig,Public\u0026#34;,[System.Reflection.CallingConventions]::Standard,$Parameters).SetImplementationFlags(\u0026#34;Runtime,Managed\u0026#34;);$TypeBuilder.DefineMethod(\u0026#34;Invoke\u0026#34;,\u0026#34;Public,HideBySig,NewSlot,Virtual\u0026#34;,$ReturnType,$Parameters).SetImplementationFlags(\u0026#34;Runtime,Managed\u0026#34;);return $TypeBuilder.CreateType();}function gproc{Param ([Parameter(Position=0,Mandatory=$True)] [String] $Module,[Parameter(Position=1,Mandatory=$True)] [String] $Procedure);$SystemAssembly=[AppDomain]::CurrentDomain.GetAssemblies()|Where-Object{$_.GlobalAssemblyCache -And $_.Location.Split(\u0026#34;\\\\\u0026#34;)[-1].Equals(\u0026#34;System.dll\u0026#34;)};$UnsafeNativeMethods=$SystemAssembly.GetType(\u0026#34;Microsoft.Win32.UnsafeNativeMethods\u0026#34;);return $UnsafeNativeMethods.GetMethod(\u0026#34;GetProcAddress\u0026#34;).Invoke($null,@([Syste\\x02Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr),$UnsafeNativeMethods.GetMethod(\u0026#34;GetModuleHandle\u0026#34;).Invoke($null,@($Module)))),$Procedure));}[Byte[]]$sc32 = [System.Convert]::FromBase64String($env:l+$env:O);$a=Get-Date;if($a.Month -ge 2){exit;}[Uint32[]] $op=0;$r=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll VirtualProtect),(gdelegate @([Byte[]],[UInt32],[UInt32],[UInt32[]]) ([IntPtr])))).Invoke($sc32,$sc32.Length,0x40,$op);if($r -eq 0){$pr=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll VirtualAlloc),(gdelegate @([IntPtr],[UInt32],[UInt32],[UInt32]) ([UInt32])))).Invoke(0,$sc32.Length,0x3000,0x40);if($pr -ne 0){$memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc msvcrt.dll memset),(gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr]))));for ($i=0;$i -le ($sc32.Length-1);$i++) {$memset.Invoke(($pr+$i), $sc32[$i], 1)};([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll CreateThread),(gdelegate @([IntPtr],[UInt32],[UInt32],[UInt32],[UInt32],[IntPtr]) ([IntPtr])))).Invoke(0,0,$pr,$pr,0,0);}}else{([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll CreateThread),(gdelegate @([IntPtr],[UInt32],[Byte[]],[Byte[]],[UInt32],[IntPtr]) ([IntPtr])))).Invoke(0,0,$sc32,$sc32,0,0);}sleep(1200);}catch{}exit; \u0026hellip;..wow. Okay, that is a mess. Let\u0026rsquo;s clean that up, shall we?\nsleep(90); try { function gdelegate { Param( [Parameter(Position=0,Mandatory=$True)] [Type[]] $Parameters, [Parameter(Position=1)] [Type] $ReturnType=[Void] ); $TypeBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly( (New-Object System.Reflection.AssemblyName(\u0026#34;ReflectedDelegate\u0026#34;)), [System.Reflection.Emit.AssemblyBuilderAccess]::Run ).DefineDynamicModule(\u0026#34;InMemoryModule\u0026#34;, $false).DefineType( \u0026#34;XXX\u0026#34;, \u0026#34;Class,Public,Sealed,AnsiClass,AutoClass\u0026#34;, [System.MulticastDelegate] ); $TypeBuilder.DefineConstructor( \u0026#34;RTSpecialName,HideBySig,Public\u0026#34;, [System.Reflection.CallingConventions]::Standard, $Parameters ).SetImplementationFlags(\u0026#34;Runtime,Managed\u0026#34;); $TypeBuilder.DefineMethod( \u0026#34;Invoke\u0026#34;, \u0026#34;Public,HideBySig,NewSlot,Virtual\u0026#34;, $ReturnType, $Parameters ).SetImplementationFlags(\u0026#34;Runtime,Managed\u0026#34;); return $TypeBuilder.CreateType(); } function gproc { Param( [Parameter(Position=0, Mandatory=$True)] [String] $Module, [Parameter(Position=1, Mandatory=$True)] [String] $Procedure ); $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object{$_.GlobalAssemblyCache -And $_.Location.Split(\u0026#34;\\\u0026#34;)[-1].Equals(\u0026#34;System.dll\u0026#34;)}; $UnsafeNativeMethods = $SystemAssembly.GetType(\u0026#34;Microsoft.Win32.UnsafeNativeMethods\u0026#34;); return $UnsafeNativeMethods.GetMethod(\u0026#34;GetProcAddress\u0026#34;).Invoke( $null, @([SysteRuntime.InteropServices.HandleRef]( New-Object System.Runtime.InteropServices.HandleRef( (New-Object IntPtr), $UnsafeNativeMethods.GetMethod(\u0026#34;GetModuleHandle\u0026#34;).Invoke($null, @($Module)) ) ), $Procedure) ); } [Byte[]] $sc32 = [System.Convert]::FromBase64String($env:l + $env:O); [Uint32[]] $op = 0; $r = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll VirtualProtect), (gdelegate @([Byte[]], [UInt32], [UInt32], [UInt32[]]) ([IntPtr])) ) ).Invoke($sc32, $sc32.Length, 0x40, $op); if($r -eq 0) { $pr = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll VirtualAlloc), (gdelegate @([IntPtr], [UInt32], [UInt32],[UInt32]) ([UInt32])) ) ).Invoke(0, $sc32.Length, 0x3000, 0x40); if($pr -ne 0) { $memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc msvcrt.dll memset), (gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr])) ) ); for ($i = 0; $i -le ($sc32.Length - 1); $i++) { $memset.Invoke(($pr + $i), $sc32[$i], 1)}; ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll CreateThread), (gdelegate @([IntPtr], [UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) ([IntPtr])) )).Invoke(0, 0, $pr, $pr, 0, 0); } } else { ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll CreateThread), (gdelegate @([IntPtr],[UInt32],[Byte[]],[Byte[]],[UInt32],[IntPtr]) ([IntPtr])) )).Invoke(0, 0, $sc32, $sc32, 0, 0); } sleep(1200); } catch{} exit; Note: I appologize for this formatting if some of it isn\u0026rsquo;t valid powershell, mine is a bit weak so I can\u0026rsquo;t vouch for its accuracy but this was the easiest to read.\nI started looking at what this was doing and I pretty quickly identified that it was spawning a process with Windows API calls from powershell that would execute some kind of payload. How? You\u0026rsquo;ll notice that these functions basically define the C functions that are being accessed by powershell and which DLLs to use for them. Then, this line stands out and it is really the entry point for this script\n[Byte[]] $sc32 = [System.Convert]::FromBase64String($env:l + $env:O); This is going to take the environment variables l and O and convert them from base64 into a byte array called $sc32\u0026hellip;.recall that there is a string in the original G0AL.BAT that said Tested on Win7SP1 32-bit OS \u0026mdash; this was intended for a 32-bit OS, so $sc32 probably stands for shellcode 32-bit. Right, so this is gonna be what we wanna know what\u0026rsquo;s happening with. So next we have,\n[Uint32[]] $op = 0; $r = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll VirtualProtect), (gdelegate @([Byte[]], [UInt32], [UInt32], [UInt32[]]) ([IntPtr])) ) ).Invoke($sc32, $sc32.Length, 0x40, $op); This function is defined in the Windows API Documentation as shown:\nBOOL WINAPI VirtualProtect( _In_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flNewProtect, _Out_ PDWORD lpflOldProtect ); As you can see, this is getting the function VirtualProtect from kernel32.dll, defining its parameters as byte[], uint32, uint32, uint32[], and its return type as an int*, and then calling the function with the shellcode, the length of the shellcode, 0x40 which indicates PAGE_EXECUTE_READWRITE and then 0 or NULL as the final parameter \u0026mdash; not sure why this is there as it only takes 3 parameters. Then we have this block of code:\nif($r -eq 0) { $pr = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll VirtualAlloc), (gdelegate @([IntPtr], [UInt32], [UInt32],[UInt32]) ([UInt32])) ) ).Invoke(0, $sc32.Length, 0x3000, 0x40); if($pr -ne 0) { $memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc msvcrt.dll memset), (gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr])) ) ); for ($i = 0; $i -le ($sc32.Length - 1); $i++) { $memset.Invoke(($pr + $i), $sc32[$i], 1)}; ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll CreateThread), (gdelegate @([IntPtr], [UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) ([IntPtr])) )).Invoke(0, 0, $pr, $pr, 0, 0); } } else { ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer( (gproc kernel32.dll CreateThread), (gdelegate @([IntPtr],[UInt32],[Byte[]],[Byte[]],[UInt32],[IntPtr]) ([IntPtr])) )).Invoke(0, 0, $sc32, $sc32, 0, 0); } Really, all this does is say \u0026ldquo;Hey, if I was unable to just reserve the size needed for the shellcode and make it read/write/execute\u0026rdquo;. Then it will memset each byte of the VirtualAlloc\u0026rsquo;d memory and create a thread for each execution of VirtualAlloc every time it does memset. If it was able to reserve the size needed for the shellcode, it would simply do CreateThread for the shellcode.\nNow, we need to know what that shellcode is in order to figure out what\u0026rsquo;s going on in this code. If we combine l and O together, we get the base64 string:\n2eub2XQk9DHSsncxyWSLcTCLdgyLdhyLRgiLfiCLNjhPGHXzWQHR/+Fgi2wkJItFPItUKHgB6otKGItaIAHr4zRJizSLAe4x/zHA/KyEwHQHwc8NAcfr9Dt8JCh14YtaJAHrZosMS4taHAHriwSLAeiJRCQcYcOyCCnUieWJwmiOTg7sUuif////iUUEu37Y4nOHHCRS6I7///+JRQhobGwgQWgzMi5kaHVzZXIw24hcJAqJ5lb/VQSJwlC7qKJNvIccJFLoX////2hyb1ggaGRNaWNoVHJlbjHbiFwkConjaCF9WCBoZ2FpbmhzTWVBaCFJdEloaGVyZWhsbG9UaEZ7SGVoVE1DVDHJiEwkHonhMdJSU1FS/9AxwFD/VQg= Which decodes to\u0026hellip;\nIn [1]: import base64 In [2]: base64.b64decode(\u0026#39;2eub2XQk9DHSsncxyWSLcTCLdgyLdhyLRgiLfiCLNjhPGHXzWQHR/+Fgi2wkJItFPItUKHgB6otKGItaIAHr4zRJizSLAe4x/zHA/KyEwHQHwc8NAcfr9Dt8JCh14YtaJAHrZosMS4taHAHriwSLAeiJRCQcYcOyCCnUieWJwmiOTg7 ...: sUuif////iUUEu37Y4nOHHCRS6I7///+JRQhobGwgQWgzMi5kaHVzZXIw24hcJAqJ5lb/VQSJwlC7qKJNvIccJFLoX////2hyb1ggaGRNaWNoVHJlbjHbiFwkConjaCF9WCBoZ2FpbmhzTWVBaCFJdEloaGVyZWhsbG9UaEZ7SGVoVE1DVDHJiEwkHonhMdJS ...: U1FS/9AxwFD/VQg=\u0026#39;) Out[2]: \u0026#39;\\xd9\\xeb\\x9b\\xd9t$\\xf41\\xd2\\xb2w1\\xc9d\\x8bq0\\x8bv\\x0c\\x8bv\\x1c\\x8bF\\x08\\x8b~ \\x8b68O\\x18u\\xf3Y\\x01\\xd1\\xff\\xe1`\\x8bl$$\\x8bE\u0026lt;\\x8bT(x\\x01\\xea\\x8bJ\\x18\\x8bZ \\x01\\xeb\\xe34I\\x8b4\\x8b\\x01\\xee1\\xff1\\xc0\\xfc\\xac\\x84\\xc0t\\x07\\xc1\\xcf\\r\\x01\\xc7\\xeb\\xf4;|$(u\\xe1\\x8bZ$\\x01\\xebf\\x8b\\x0cK\\x8bZ\\x1c\\x01\\xeb\\x8b\\x04\\x8b\\x01\\xe8\\x89D$\\x1ca\\xc3\\xb2\\x08)\\xd4\\x89\\xe5\\x89\\xc2h\\x8eN\\x0e\\xecR\\xe8\\x9f\\xff\\xff\\xff\\x89E\\x04\\xbb~\\xd8\\xe2s\\x87\\x1c$R\\xe8\\x8e\\xff\\xff\\xff\\x89E\\x08hll Ah32.dhuser0\\xdb\\x88\\\\$\\n\\x89\\xe6V\\xffU\\x04\\x89\\xc2P\\xbb\\xa8\\xa2M\\xbc\\x87\\x1c$R\\xe8_\\xff\\xff\\xffhroX hdMichTren1\\xdb\\x88\\\\$\\n\\x89\\xe3h!}X hgainhsMeAh!ItIhherehlloThF{HehTMCT1\\xc9\\x88L$\\x1e\\x89\\xe11\\xd2RSQR\\xff\\xd01\\xc0P\\xffU\\x08\u0026#39; I looked up this initial byte sequence \u0026mdash; D9 EB 9B D9 \u0026mdash; and found a snort rule on Github!\n# alert ip $EXTERNAL_NET any -\u0026gt; $HOME_NET any (msg:\u0026#34;INDICATOR-SHELLCODE Metasploit payload windows_messagebox\u0026#34;; content:\u0026#34;|D9 EB 9B D9 74 24 F4 31 D2 B2 77 31 C9 64 8B 71 30 8B 76 0C 8B 76 1C 8B 46 08 8B 7E 20 8B 36 38 4F 18 75 F3 59 01 D1 FF E1 60 8B 6C 24 24 8B 45|\u0026#34;; fast_pattern:only; classtype:shellcode-detect; sid:30472; rev:1;) So this is a Metasploit payload for a windows message box, huh? I dumped it into a file and was hoping to see something I could disassemble, but it didn\u0026rsquo;t look like it was going to be that easy :/\n$ file shellcode shellcode: data So then I decided to do it the hard way, by hand!\n$ strings shellcode ;|$(u hll Ah32.dhuser0 hroX hdMichTren1 h!}X hgainhsMeAh!ItIhherehlloThF{HehTMCT1 RSQR Let\u0026rsquo;s split this out a bit\u0026hellip;h!}X hgainhsMeAh!ItIhherehlloThF{HehTMCT1 looks like all the h are gonna be split between words. So, I started piecing it together that every time an h ocurred, it was time for a new split in the string. There was an h after every 4 characters! Now we have something like this:\n!}X gain sMeA !ItI here lloT F{He TMCT 1 I think that 1 and X are junk indicating the front and end of the word or something, so now we can see that this is sorta the flag, but backwards. If we concatenate to the end of the string in order from bottom to top, we get the flag: TMCTF{HelloThere!ItIsMeAgain!}\nI definitely need to brush up on my powershell\u0026hellip;\n~Joel\n","date":"25 June 2017","externalUrl":null,"permalink":"/posts/tmctf-2017-re300-powershell/","section":"Posts","summary":"","title":"TrendMicro CTF 2017 - RE300","type":"post"},{"content":"This was the final reversing challenge in AlexCTF 2017. You can download it here (md5: ca6fd408e10d7d358bb10c8124ff7862).\nThe challenge description was:\nBeing said that move instruction is enough to build a complete computer, anyway move on while you can.\nmove\nThe first thing I did was to put this into my vagrant box, run file on it, and then execute it. $ file move move: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped $ ./move Guess a flag: flag Wrong Flag! Well, that was somewhat expected. Let\u0026rsquo;s look under the hood, shall we? I opened the binary in Binary Ninja and there was not a whole lot of stuff to look at in the assembly. Taking a look at the strings next, I noticed something very helpful: the binary had been packed with UPX Packer.\nSo I took to Google and found the UPX packer version 3.91 that was used to pack this binary (found here). I downloaded it, extracted the archive, and looked at the usage. $ ./upx-3.91-amd64_linux/upx -h Ultimate Packer for eXecutables Copyright (C) 1996 - 2013 UPX 3.91 Markus Oberhumer, Laszlo Molnar \u0026amp; John Reiser Sep 30th 2013 Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file.. Commands: -1 compress faster -9 compress better --best compress best (can be slow for big files) -d decompress -l list compressed file -t test compressed file -V display version number -h give this help -L display software license Options: -q be quiet -v be verbose -oFILE write output to \u0026#39;FILE\u0026#39; -f force compression of suspicious files --no-color, --mono, --color, --no-progress change look Compression tuning options: --brute try all available compression methods \u0026amp; filters [slow] --ultra-brute try even more compression variants [very slow] Backup options: -k, --backup keep backup files --no-backup no backup files [default] Overlay options: --overlay=copy copy any extra data attached to the file [default] --overlay=strip strip any extra data attached to the file [DANGEROUS] --overlay=skip don\u0026#39;t compress a file with an overlay Options for djgpp2/coff: --coff produce COFF output [default: EXE] Options for dos/com: --8086 make compressed com work on any 8086 Options for dos/exe: --8086 make compressed exe work on any 8086 --no-reloc put no relocations in to the exe header Options for dos/sys: --8086 make compressed sys work on any 8086 Options for ps1/exe: --8-bit uses 8 bit size compression [default: 32 bit] --8mib-ram 8 megabyte memory limit [default: 2 MiB] --boot-only disables client/host transfer compatibility --no-align don\u0026#39;t align to 2048 bytes [enables: --console-run] Options for watcom/le: --le produce LE output [default: EXE] Options for win32/pe, rtm32/pe \u0026amp; arm/pe: --compress-exports=0 do not compress the export section --compress-exports=1 compress the export section [default] --compress-icons=0 do not compress any icons --compress-icons=1 compress all but the first icon --compress-icons=2 compress all but the first icon directory [default] --compress-icons=3 compress all icons --compress-resources=0 do not compress any resources at all --keep-resource=list do not compress resources specified by list --strip-relocs=0 do not strip relocations --strip-relocs=1 strip relocations [default] Options for linux/elf: --preserve-build-id copy .gnu.note.build-id to compressed output file.. executables to (de)compress This version supports: AMD64-darwin.macho Mach/AMD64 ARMEL-darwin.macho Mach/ARMEL amd64-linux.elf linux/ElfAMD amd64-linux.kernel.vmlinux vmlinux/AMD64 amd64-win64.pe win64/pe arm-linux.elf linux/armel arm-linux.kernel.vmlinux vmlinux/armel arm-wince.pe arm/pe armeb-linux.elf linux/armeb armeb-linux.kernel.vmlinux vmlinux/armeb armel-linux.kernel.vmlinuz vmlinuz/armel fat-darwin.macho Mach/fat i086-dos16.com dos/com i086-dos16.exe dos/exe i086-dos16.sys dos/sys i386-bsd.elf.execve BSD/386 i386-darwin.macho Mach/i386 i386-dos32.djgpp2.coff djgpp2/coff i386-dos32.tmt.adam tmt/adam i386-dos32.watcom.le watcom/le i386-freebsd.elf BSD/elf386 i386-linux.elf linux/elf386 i386-linux.elf.execve linux/386 i386-linux.elf.shell linux/sh386 i386-linux.kernel.bvmlinuz bvmlinuz/386 i386-linux.kernel.vmlinux vmlinux/386 i386-linux.kernel.vmlinuz vmlinuz/386 i386-netbsd.elf netbsd/elf386 i386-openbsd.elf opnbsd/elf386 i386-win32.pe win32/pe m68k-atari.tos atari/tos mips-linux.elf linux/mipseb mipsel-linux.elf linux/mipsel mipsel.r3000-ps1 ps1/exe powerpc-darwin.macho Mach/ppc32 powerpc-linux.elf linux/ElfPPC powerpc-linux.kernel.vmlinux vmlinux/ppc32 UPX comes with ABSOLUTELY NO WARRANTY; for details visit http://upx.sf.net\nSo the argument I needed to use was -d to decompress. $ ./upx-3.91-amd64_linux/upx -d move -o move_unpacked Ultimate Packer for eXecutables Copyright (C) 1996 - 2013 UPX 3.91 Markus Oberhumer, Laszlo Molnar \u0026amp; John Reiser Sep 30th 2013 File size Ratio Format Name -------------------- ------ ----------- ----------- 10308504 \u0026lt;- 2619128 25.41% netbsd/elf386 move_unpacked Unpacked 1 file.\nNow I had an unpacked binary. Turns out that even though this was the correct thing to do, this actually was even worse to look at! Literally every instruction was a MOV\u0026hellip;?! I did some intense googling and found out that this binary was created using a tool called movfuscator. I tried to find a deobfuscator for it, however the only one that I could find did not do any good. So I kept looking and came across an interesting writeup on movfuscator that had a tracer program that would look at 1-byte memory writes using Intel PIN. So, I downloaded Intel PIN, compiled the program as instructed, and tried to use it. I also came across this other CTF writeup that used the same tool I was using in a CTF context. The way it was using it was to look at the patterns in the 1-byte writes, and see what was happening in response to changes in the input.\nI applied this same type of thinking to the output that I was receiving. Since I knew that the flag started with ALEXCTF{, I used this to understand the output from the tracer program.\nFirst, I tested using an empty string. $ (echo \u0026#34;\u0026#34; | ../../../pin -t obj-ia32/tracer.so -- ../../../../move_unpacked); xxd trace-1byte-writes.bin Guess a flag: Wrong Flag! 0000000: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000010: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000020: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000030: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000040: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000050: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000060: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000070: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000080: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000090: 0101 0000 ....\nThen, I started adding the flag format characters in one at a time and observed the changes. $ (echo \u0026#34;A\u0026#34; | ../../../pin -t obj-ia32/tracer.so -- ../../../../move_unpacked); xxd trace-1byte-writes.bin Guess a flag: Wrong Flag! 0000000: 0000 0100 0101 0000 0101 0000 0101 0000 ................ 0000010: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000020: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000030: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000040: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000050: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000060: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000070: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000080: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000090: 0101 0000 .... $ (echo \u0026#34;AL\u0026#34; | ../../../pin -t obj-ia32/tracer.so -- ../../../../move_unpacked); xxd trace-1byte-writes.bin Guess a flag: Wrong Flag! 0000000: 0000 0100 0000 0100 0101 0000 0101 0000 ................ 0000010: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000020: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000030: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000040: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000050: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000060: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000070: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000080: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000090: 0101 0000 ....\nI noticed that there seemed to be 1 byte for each character in the flag. 0101 0000 indicated an incorrect character, and 0000 0100 indicated a correct character. I then started to write a script that would do this for me automagically, but realized I had an issue that would make things take a lot longer. My idea was to simply loop over all characters and check the binary output and see if I had put in a correct character. But what about lowercase vs. uppercase characters? I had done some preliminary scripting and realized that if I had to check 52 letters + symbols + digits, this would take a lot longer than if I could just check lowercase.\nLuckily, there was also an output for this! $ (echo \u0026#34;Al\u0026#34; | ../../../pin -t obj-ia32/tracer.so -- ../../../../move_unpacked); xxd trace-1byte-writes.bin Guess a flag: Wrong Flag! 0000000: 0000 0100 0000 0000 0101 0000 0101 0000 ................ 0000010: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000020: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000030: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000040: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000050: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000060: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000070: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000080: 0101 0000 0101 0000 0101 0000 0101 0000 ................ 0000090: 0101 0000 ....\nAn output of 0000 0000 indicated the right letter but wrong case. So I then finished my script so that I checked for these cases and brute forced the flag. Here is my full solution code from string import ascii_lowercase, digits import os allChars = digits + \u0026#39;_}\u0026#39; + ascii_lowercase flag = \u0026#39;ALEXCTF{\u0026#39; wrong = \u0026#39;\\x01\\x01\\x00\\x00\u0026#39; right = \u0026#39;\\x00\\x00\\x01\\x00\u0026#39; case = \u0026#39;\\x00\\x00\\x00\\x00\u0026#39; def tryFlag(f): os.system(\u0026#39;(echo \u0026#34;{}\u0026#34; | ../../../pin -t obj-ia32/tracer.so -- ../../../../move) \u0026gt; /dev/null\u0026#39;.format(f)) data = open(\u0026#39;trace-1byte-writes.bin\u0026#39;, \u0026#39;rb\u0026#39;).read() offset = len(f) * 4 return data[offset - 4:offset] while flag[:-1] != \u0026#39;}\u0026#39;: for c in allChars: result = tryFlag(flag + c) if result == case: c = c.upper() result = tryFlag(flag + c) if result == right: flag += c print flag break\nThe resulting flag was ALEXCTF{M0Vfusc4t0r_w0rk5_l1ke_m4g1c}\n~Joel\n","date":"6 February 2017","externalUrl":null,"permalink":"/posts/alexctf-2017-re5-packed-movement/","section":"Posts","summary":"","title":"AlexCTF 2017 - RE5: Packed Movement (350)","type":"post"},{"content":"The situation was given as such:\nToday, our 3-disk NAS has failed. Please recover flag.\ndeadnas.7z\nUpon extraction, the file list was as such root@kali:~# 7z x deadnas.7z 7-Zip [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18 p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,2 CPUs) Processing archive: deadnas.7z Extracting deadnas/disk0 Extracting deadnas/disk1 Extracting deadnas/disk2 Extracting deadnas Everything is Ok Folders: 1 Files: 3 Size: 1048588 Compressed: 499418 root@kali:~# ls -la deadnas total 1036 drwxr-xr-x 2 root root 4096 Sep 2 19:24 . drwxr-xr-x 17 root root 4096 Sep 7 16:50 .. -rw-r--r-- 1 root root 524288 Jun 16 13:56 disk0 -rw-r--r-- 1 root root 12 Jun 16 14:01 disk1 -rw-r--r-- 1 root root 524288 Jun 16 13:56 disk2\nand running file on all the files showed a FAT32 file, a text file, and a data file root@kali:~# cd deadnas/ root@kali:~/deadnas# file * disk0: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID \u0026#34;mkfs.fat\u0026#34;, sectors/cluster 4, root entries 512, sectors 2048 (volumes \u0026lt;=32 MB) , Media descriptor 0xf8, sectors/FAT 2, sectors/track 32, heads 64, serial number 0x867314a9, unlabeled, FAT (12 bit) disk1: ASCII text disk2: data\nNow, we know that this is a NAS device and that there are 3 drives. The first thing I thought to do was open the first disk (disk0) in dff GUI and see what it shows.\nOf cource, it couldn\u0026rsquo;t be that simple. We can see here that there are a bunch of C files for some kind of source code, but nothing that looks like a flag. In addition to that, if we look at the contents of some of these files, such as configure, we can see that they appear to be broken in some parts, plaintext in others, and it only goes up to 0x200, or 512.\nNow, if you know much about NAS\u0026rsquo;s, more often than not you put your drives into a RAID configuration. The most common RAID types that are used are 0, 1, 5, 6, and 10 and each has it\u0026rsquo;s own advantages and disadvantages. Due to how we can see the data is not sequantial and consistent, it is safe to assume that these drives are likely in RAID and it is most likely not RAID 0, 1, and 10, especially with only 3 drives in the array.\nThat leaves RAID 5 and 6. Now, to understand how these are especially good choices based on that this is a challenge and we are assuming it can be solved, we must first understand how RAID 5 and 6 behave. Since RAID 6 is based on RAID 5, I will briefly cover how RAID 5 works in a simplified form.\nIn RAID 5, you must have at least 3 drives in your array. When data is written to the drives, it is striped into sections based on your configuration. Every drive gets part of the data every stripe, sequentially, and one of the drives is used for parity. When data is written to the drives, it is XOR\u0026rsquo;d and that result is written to the block on the parity drive (in addition, the drive used for parity rotates forwards or backwards each block). The unique nature of the XOR bitwise function is that it is totally reversible, as long as you have the other factors in the equation.\nimage courtesy Wikipedia\nFor example,\nA XOR B = C C XOR B = A C XOR A = B As you can see, this is very advantagous because it allows us to recover data across stripes, not just for a single drive. RAID 6 is very similar to RAID 5 in that it does the same thing, however it adds another parity block for additonal redundancy and the computation of the second parity block is more complicated than a simple XOR operation.\nI took the liberty to assume that it was likely RAID 5 (which was later confirmed with hints provided to the challenge) and wrote out a simple script in python that would be able to rebuild our missing drive and combine the 3 drives into a single disk image.\nwith open(\u0026#39;disk0\u0026#39;, \u0026#39;rb\u0026#39;) as f: disk0 = f.read() with open(\u0026#39;disk2\u0026#39;, \u0026#39;rb\u0026#39;) as f: disk2 = f.read() #XOR data in disk0 and disk2 to rebuild disk1 disk1 = \u0026#39;\u0026#39;.join([chr(ord(d0) ^ ord(d2)) for d0, d2 in zip(disk0, disk2)]) #create a RAID 5 \u0026#34;array\u0026#34; raidArray = [disk0, disk1, disk2] #our block size is 512 (remember from earlier how it cut off at 0x200?) BS = 512 #open our output disk file with open(\u0026#39;disk\u0026#39;, \u0026#39;wb\u0026#39;) as f: #iterate over the blocks (disk length / 512 bytes per block) for blockIndex in xrange(len(disk0) / BS): #calculate our parity drive index rotating backwards, starting with the last drive parityIndex = (2 - blockIndex) % 3 #iterate over the 3 drives in the array for driveIndex in xrange(3): #make sure not to pull data from the parity drive, we only want actual data if driveIndex != parityIndex: #calculate our starting byte position blockStart = blockIndex * BS #write the data from the starting byte to 512 bytes after it from the target drive f.write(raidArray[driveIndex][blockStart:blockStart + BS]) After execution, we are left with a file, disk, with a filesize of 1024kb and it\u0026rsquo;s file output is root@kali:~/deadnas# file disk disk: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID \u0026#34;mkfs.fat\u0026#34;, sectors/cluster 4, root entries 512, sectors 2048 (volumes \u0026lt;=32 MB) , Media descriptor 0xf8, sectors/FAT 2, sectors/track 32, heads 64, serial number 0x867314a9, unlabeled, FAT (12 bit)\nLooks promising, let\u0026rsquo;s see if it\u0026rsquo;s valid\u0026hellip;\nWe mount the image using losetup root@kali:~/deadnas# losetup /dev/loop0 disk\nAnd we are left with a file, flag.jpg, in our mounted disk! P.S. Remember those C files we saw in dff? That was the source code to which-2.21, just to throw us off\u0026hellip;\n~Joel\n","date":"7 September 2016","externalUrl":null,"permalink":"/posts/tokyo-westerns-ctf-2016-deadnas/","section":"Posts","summary":"","title":"Tokyo Westerns CTF 2016 - Recovery 1: deadnas","type":"post"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"}]