- Name - [missing]
- Category - Reverse Engineering
- Points - 1
- Binary - Download here
The Vulnerable Space
Wednesday 10 January 2018
CTF Writeup - Flare-On 2017 - 12: [missing]
Friday 10 November 2017
CTF Writeup - Flare-On 2017 - 11: covfefe.exe
- Name - covfefe.exe
- Category - Reverse Engineering
- Points - 1
- Binary - Download here
Command Prompt
C:\>covfefe.exe
Welcome to the magic box.
Enter the password: random_stuff
You did not guess the password. Consider brute-force guessing using a script, and letting
it run for a few trillion years.
C:\>
Looking at it under IDA, the binary's code is deceptively simple and doesn't do much. We only have 3 functions with the most important being sub_401000:
bool __cdecl sub_401000(int a1, int a2, int a3, int a4) { bool result; // al@2 *(_DWORD *)(a1 + 4 * a3) -= *(_DWORD *)(a1 + 4 * a2); if ( a4 ) result = *(_DWORD *)(a1 + 4 * a3) <= 0; else result = 0; return result; }The function accepts a start position, a1, and 2 offsets, a2 and a3, and returns True if a1[a2] - a1[a3] is less than or equal to 0, else it returns False. The entire logic of this program is based solely on this instruction. We're hence dealing with a SUBLEQ VM. For those of you who would like to see an elegant way of solving this challenge, please find another write-up. Here I'll be going through a very basic and manual way of how I solved it. The SUBLEQ VM is too complicated to go through instruction by instruction so, my technique was to put RW breakpoints on the memory region that holds our input and follow any transformations that happen to it. If parts of the input are copied to another memory region, we'll put RW breakpoints on those as well and monitor them. So, let's get started by supplying the program with 'random' as input. After a few breakpoints we see that our input has been stored in location 0x40313C: Add a new RW breakpoint to cover the 0x18-byte memory region starting from 0x40313C. After hitting the new breakpoint a few times we find that the first 2 chars of our input were copied to locations 0x406A68 and 0x406A6C respectively: Put a RW breakpoint on these 2 new locations. Continuing with the execution we encounter the first operation on the first character: The value 0x72, which is hex for 'r', the first character of our input, has been changed to 0x6AE. In other words, it has been multiplied by 0xF. The resultant value is then squared 7 times, which gives us 0x35700: The focus now shifts to the 2nd char, which is 'a' in our case. This is copied to location 0x405FC4. At the same time, the value 0x7F is copied to location 0x405FC0. Both these values can be seen in the following screenshot: By putting a breakpoint on each of these memory regions containing the 2 values we can see them change with each iteration. This part wasn't that straight forward to deduce using my method. After a while the values stop changing and we get the following scenario: The original values, 0x7F and 0x61, which resided in locations 0x405FC4 and 0x405FC0 respectively, have been modified to show 0xFFFFFFFF. At the same time though, location 0x405FDC contains the value 0x20. This is a simple counter which tells the SUBLEQ VM to stop operating on these 2 values when it reaches 0x20. More importantly though is location 0x405FD0 which contains the value 0x1E. So which operation on values 0x7F and 0x61 gives 0x1E? Well, it's XOR of course. The SUBLEQ VM now copies the resultant to where the original value was: So at this point we have values 0x35700 and 0x1E. A few breakpoints later, we can see that the values have been OR'd and the result replaces the first value: To be honest the operation could have been interpreted as an XOR as it produces the same result. In fact, it doesn't matter if we use OR or XOR, the challenge is still possible with either. Continuing, we encounter this situation: The value 0x35E8A overwrites the second value. Then, our result, which resides at location 0x406A68, and this new value are subtracted from each other. If we continue execution we notice that the whole process is restarted with the next 2 characters of our input. This strongly suggests that the value 0x35E8A is what our result should have been. The strategy to completing the challenge is now straight forward: collect all the comparison results and for each, reverse the algorithm to obtain 2 characters of the input each time. First, let us reiterate what the algorithm is for each 2 characters of our input:
- Multiply the first char by 0xF and square the value 7 times
- XOR the 2nd character by 0x7F
- OR the 2 results together
- Compare the final result to a fixed value
import string, sys sols = [0x35E8A,0x2DF13,0x2F58E,0x2C89E,0x3391B,0x2C88D,0x2F59B,0x36D9C,0x36616,0x340A0,0x2D79B,0x2C89E,0x2DF0C,0x36D8D,0x2EE0A,0x331FF] def try_sol(c1, c2, sol): c1 *= 0xF for x in range(7): c1 += c1 c2 ^= 0x7F c12 = c1 | c2 if c12 == sol: return True return False for sol in sols: for c1 in string.printable + '\x00': for c2 in string.printable + '\x00': if try_sol(ord(c1),ord(c2), sol): sys.stdout.write(c1) sys.stdout.write(c2)Running the script:
Command Prompt
C:\>python covfefe_sol.py
subleq_and_reductio_ad_absurdum
C:\>
CTF Writeup - Flare-On 2017 - 10: shell.php
- Name - shell.php
- Category - Reverse Engineering
- Points - 1
- Binary - Download here
<?php $o__o_ = base64_decode('QAYLHhIJ ... [snip] ... kCEh8NSh4PEA4eGFsaGXoo'); $o_o = isset($_POST['o_o']) ? $_POST['o_o'] : ""; $o_o = md5($o_o) . substr(MD5(strrev($o_o)) , 0, strlen($o_o)); for ($o___o = 0; $o___o < 2268; $o___o++) { $o__o_[$o___o] = chr((ord($o__o_[$o___o]) ^ ord($o_o[$o___o])) % 256); $o_o.= $o__o_[$o___o]; } if (MD5($o__o_) == '43a141570e0c926e0e3673216a4dd73d') { if (isset($_POST['o_o'])) @setcookie('o_o', $_POST['o_o']); $o___o = create_function('', $o__o_); unset($o_o, $o__o_); $o___o(); } else { echo '<form method="post" action="shell.php"><input type="text" name="o_o" value=""/><input type="submit" value=">"/></form>'; } ?>The script is easy enough to understand. It basically does the following:
- Base64 decodes a massive string (line 2)
- Accepts a password via a POST request (line 3)
- Concatenates it's MD5 and the substring of the MD5 of the reverse string (line 4)
- Uses the result as the key to XOR the massive string (lines 6 - 10)
- Creates a new function from the decrypted text (line 15)
- Calls the function (line 17)
import base64 import string encoded = "QAYLHhIJ ... [snip] ... kCEh8NSh4PEA4eGFsaGXoo" decoded = base64.b64decode(encoded) possible_chars = "0123456789abcdef" for key_len in xrange(33, 65): print "-- Key Length: " + str (key_len) + " --" for key_pos in xrange(0, key_len): potential_sol = [] for possible_char in possible_chars: temp_possible_char = possible_char possible = True for pos in range(key_pos, len(decoded), key_len): temp_possible_char = chr((ord(decoded[pos]) ^ ord(temp_possible_char))) if temp_possible_char not in string.printable: possible = False break if possible: potential_sol.append(possible_char) if potential_sol: print key_pos, ":", potential_solRunning the script we get:
Command Prompt
C:\>python possible_solutions.py
-- Key Length: 33 --
-- Key Length: 34 --
-- Key Length: 35 --
-- Key Length: 36 --
-- Key Length: 37 --
-- Key Length: 38 --
-- Key Length: 39 --
-- Key Length: 40 --
-- Key Length: 41 --
-- Key Length: 42 --
9 : ['4', '6', '7']
-- Key Length: 43 --
-- Key Length: 44 --
-- Key Length: 45 --
-- Key Length: 46 --
-- Key Length: 47 --
-- Key Length: 48 --
-- Key Length: 49 --
-- Key Length: 50 --
-- Key Length: 51 --
31 : ['e']
-- Key Length: 52 --
-- Key Length: 53 --
-- Key Length: 54 --
-- Key Length: 55 --
49 : ['c', 'f']
-- Key Length: 56 --
39 : ['c']
-- Key Length: 57 --
-- Key Length: 58 --
-- Key Length: 59 --
16 : ['a', 'd', 'f']
-- Key Length: 60 --
-- Key Length: 61 --
-- Key Length: 62 --
-- Key Length: 63 --
-- Key Length: 64 --
0 : ['c', 'd', 'e']
1 : ['a', 'b', 'c', 'd', 'e']
2 : ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
3 : ['8', '9']
4 : ['2', '3', '4', '5', '6']
5 : ['0', '1', '2', '3', '4', '6', '7', '8']
6 : ['b', 'c', 'd', 'f']
7 : ['8', '9']
8 : ['0', '2', '4', '5']
9 : ['a', 'b', 'f']
10 : ['0', '2', '3', '4', '5', '6', '7', '8', '9']
11 : ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
12 : ['b', 'c', 'd']
13 : ['8', '9']
14 : ['0', '2', '3', '5']
15 : ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
16 : ['a', 'b', 'c', 'd', 'e']
17 : ['a', 'b', 'c', 'd', 'e', 'f']
18 : ['b', 'c', 'd', 'f']
19 : ['2', '3', '4', '5', '7']
20 : ['2', '3', '7']
21 : ['5', '6', '7']
22 : ['0', '1', '2', '4', '5', '6', '7', '8', '9']
23 : ['0', '1', '5', '6', '7']
24 : ['8', '9']
25 : ['a', 'b', 'c', 'd', 'e', 'f']
26 : ['b', 'c', 'd', 'e']
27 : ['8', '9']
28 : ['d', 'e']
29 : ['8', '9']
30 : ['0', '1', '2', '3', '4', '5', '6', '7', '9']
31 : ['a', 'b', 'c', 'd', 'e', 'f']
32 : ['0', '1', '2', '3', '5', '6', '7', '8', '9']
33 : ['0', '1', '2', '3', '4', '5', '6', '7', '9']
34 : ['0', '1', '3', '6', '7']
35 : ['b', 'c', 'd', 'e']
36 : ['d', 'e', 'f']
37 : ['0', '1', '4', '6', '7']
38 : ['2', '3', '4', '5']
39 : ['0', '1', '4', '6', '7']
40 : ['1', '2', '3', '4', '5', '6', '7', '8', '9']
41 : ['0', '3', '4', '7', '8', '9']
42 : ['a', 'e']
43 : ['a', 'f']
44 : ['b', 'c']
45 : ['0', '1', '3', '6', '7']
46 : ['8', '9']
47 : ['1', '2', '3', '5', '6', '7', '8', '9']
48 : ['d', 'e']
49 : ['0', '1', '7']
50 : ['0', '1', '5', '6', '7']
51 : ['0', '1', '2', '3', '4', '5', '6', '7', '9']
52 : ['a', 'b', 'c', 'f']
53 : ['a', 'b', 'c', 'd', 'e', 'f']
54 : ['a', 'b', 'c', 'd', 'f']
55 : ['a', 'b', 'c', 'd', 'e', 'f']
56 : ['0', '1', '2', '3', '5', '6', '7', '8', '9']
57 : ['0', '1', '3', '6', '7']
58 : ['8', '9']
59 : ['a', 'e', 'f']
60 : ['0', '1', '2', '3', '6', '7']
61 : ['a', 'b', 'c', 'e', 'f']
62 : ['a', 'e', 'f']
63 : ['b', 'c', 'd']
C:\>
Here we've hit 2 birds with 1 stone. It is obvious from the script that the the key length is 64 as it has a potential solution for each position of the key. We also have a list of potential solutions that we can use to start decrypting the text.
So how are going to further reduce the potential solutions? Well, it's a semi-manual task; and it's at this point that the challenge becomes a bit mundane. Let's try and find out the first 4 characters of the key with this script:
import base64 import itertools encoded = "QAYLHhIJ ... [snip] ... kCEh8NSh4PEA4eGFsaGXoo" decoded = base64.b64decode(encoded) pos_key = [ ['c', 'd', 'e'], ['a', 'b', 'c', 'd', 'e'], ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], ['8', '9']] for x in itertools.product(*pos_key): key1 = ''.join(x) key = key1 + ('_' * (64 - len(key1))) decrypted = [None] * 2268 for x in range(2268): decrypted[x] = chr((ord(decoded[x]) ^ ord(key[x])) % 256) key = key + decrypted[x] print "Key :" + key1 print ''.join(decrypted)[:len(key1)] , '|', ''.join(decrypted)[64*10:(64*10)+len(key1)], '|', ''.join(decrypted)[64*31:(64*31)+len(key1)] , '|', ''.join(decrypted)[64*32:(64*32)+len(key1)] printThe script covers the first 4 characters of the 64-char key. Notice that on line 8 we put the possible solutions for the first 4 chars obtained from the previous result. For each permutation of these characters (line 10), we decrypt sections of the text (line 16), and print some parts (line 20) which we will later manually verify if they make sense or not. Running the above script:
Command Prompt
C:\>python stage1_decrypter.py
[ ... snip ... ]
Key :db68
$d=& | $kex | ost# | e="s
Key :db69
$d=' | $key | ost" | e="r
Key :db78
$d<& | $kdx | osu# | e=#s
[ ... snip ... ]
C:\>
Note the string '$key' which indicates that we're on the right track with 'db69'. Using this labour-intensive, nerve-wracking method we finally get 'db6952b84a49b934acb436418ad9d93d237df05769afc796d067bccb379f2cac and a 2nd PHP script as the decrypted text:
$d=''; $key = ""; if (isset($_POST['o_o'])) $key = $_POST['o_o']; if (isset($_POST['hint'])) $d = "www.p01.org"; if (isset($_POST['t'])) { if ($_POST['t'] == 'c') { $d = base64_decode('SDcGHg1feVUIEhsbDxFhIBIYFQY+VwMWTyAcOhEYAw4VLVBaXRsKADMXTWxrSH4ZS1IiAgA3GxYUQVMvBFdVTysRMQAaQUxZYTlsTg0MECZSGgVcNn9AAwobXgcxHQRBAxMcWwodHV5EfxQfAAYrMlsCQlJBAAAAAAAAAAAAAAAAAFZhf3ldEQY6FBIbGw8RYlAxGEE5PkAOGwoWVHgCQ1BGVBdRCAAGQVQ2Fk4RX0gsVxQbHxdKMU8ABBU9MUADABkCGHdQFQ4TXDEfW0VDCkk0XiNcRjJxaDocSFgdck9CTgpPDx9bIjQKUW1NWwhERnVeSxhEDVs0LBlIR0VlBjtbBV4fcBtIEU8dMVoDACc3ORNPI08SGDZXA1pbSlZzGU5XVV1jGxURHQoEK0x+a11bPVsCC1FufmNdGxUMGGE='); $key = preg_replace('/(.)../', '$1', $key); } if ($_POST['t'] == 's') { $d = base64_decode('VBArMg1HYn1XGAwaAw1GDCsACwkeDgABUkAcESszBEdifVdNSENPJRkrNwgcGldMHFVfSEgwOjETEE9aRlJoZFMKFzsmQRALSilMEQsXHEUrPg9ZDRAoAwkBHVVIfzkNGAgaBAhUU00AAAAAAAAAAAAAAAAASkZSVV0KDAUCHBFQHA0MFjEVHB0BCgBNTAJVX3hkAkQiFh8ESw0AG0M5MBNRGkpdWV4bVEEVdGJGRR9XGBgcAgpVCDAsCA0GGAVWBAwcBxQqKwRCGxgbVkJFR11IdHcbRFxOUkNNV0RAVXIKSgxCWk1aVkdGQVI8dxRTVl5CR0JLVAQdOStbXkRfXlxOFEULUCp2SFJIUlVGQlUtRhExMQQLJyMmIFgDTUQtYmZIRUAECB4MHhtWRHA9Dh0WSWZmWUEHHBUzYQ=='); $key = preg_replace('/.(.)./', '$1', $key); } if ($_POST['t'] == 'w') { $d = base64_decode('DycdGg1hYjl8FURaAVZxPhgNOQpdMxVIRwNKc0YDCCsDVn5sJxJMHmJJOgArB1olFA0JHQN+TlcpOgFBKUEAA1M+RVUVDjsWEy8PQUEMV3IsSgJxCFY0IkJAGVY3HV9DbQsRaU1eSxl6IR0SEykOX2gnEAwZGHJHRU0OUn4hFUUADlw8UhRPNwpaJwlZE14Df1IRDi1HS30JFlZAHnRAEQ4tR0p9CRZXQB50LFkHNgNfEgROWkVLZV1bGHVbHyJMSRFZCQtGRU0bQAFpSEtBHxsLVEdaeEEUfCd2akdKYAFaJXBdT3BeHBRFV3IdXCV1PhsUXFUBBR5hXFwwdxsab1kECFoaM0FET2pEd2owBXpAC2ZAS11sMhVmJREWVlFyDV4ldFIdcUMBWlBbcl5CSGFTUCEPW08eEyYNSgJhYjl8Tk9BCUpvDxsAODBeLwUfE08AAAAAAAAAAAAAAAAAEXFkfV1wB0ctDRM='); $key = preg_replace('/..(.)/', '$1', $key); } while(strlen($key) < strlen($d)) $key = $key.$key; $d = $d ^ $key; } if (strlen($d)) echo $d; else echo '<form action="shell.php" method="post"><input type="hidden" name="o_o" value="'.$key.'"><input type="radio" name="t" value="c"> Raytraced Checkboard<br> <input type="radio" name="t" value="s"> p01 256b Starfield<br> <input type="radio" name="t" value="w"> Wolfensteiny<br><input type="submit" value="Show"/></form>';Alas, another 3 decryption exercises. This time the key is split into 3 parts by taking every other 3rd char, and each one decrypts a different string. At this point I was completely lost as to how I'm going to decrypt these texts or obtain the key so I made a leap of faith which, luckily for me, turned out to be correct. I assumed that the decryption key was the key to the challenge, i.e. the final email which, just like in all the challenges, ends with '@flare-on.com'. If we make this assumption then the 3 partial keys in question should end with '@a-.m', 'froc' and 'leno'. The plan now is to try each of these partial keys with each of the encrypted text and hopefully get something that makes sense in one of the combinations. The following script tries '@a-.m' against the 3rd ciphertext at each position and prints out the resultant decrypted text if it is printable:
import base64 import string cb = "DycdGg1hYjl8FURaAVZxPhgNOQpdMxVIRwNKc0YDCCsDVn5sJxJMHmJJOgArB1olFA0JHQN+TlcpOgFBKUEAA1M+RVUVDjsWEy8PQUEMV3IsSgJxCFY0IkJAGVY3HV9DbQsRaU1eSxl6IR0SEykOX2gnEAwZGHJHRU0OUn4hFUUADlw8UhRPNwpaJwlZE14Df1IRDi1HS30JFlZAHnRAEQ4tR0p9CRZXQB50LFkHNgNfEgROWkVLZV1bGHVbHyJMSRFZCQtGRU0bQAFpSEtBHxsLVEdaeEEUfCd2akdKYAFaJXBdT3BeHBRFV3IdXCV1PhsUXFUBBR5hXFwwdxsab1kECFoaM0FET2pEd2owBXpAC2ZAS11sMhVmJREWVlFyDV4ldFIdcUMBWlBbcl5CSGFTUCEPW08eEyYNSgJhYjl8Tk9BCUpvDxsAODBeLwUfE08AAAAAAAAAAAAAAAAAEXFkfV1wB0ctDRM=" c = base64.b64decode(cb) key = "@a-.m" for x in range(len(c)-len(key)): temp = ''.join(chr(ord(x) ^ ord(y)) for x,y in zip(key,c[x:])) printable = True for y in temp: if y not in string.printable: printable = False break if printable: print x, tempRunning the script:
Command Prompt
C:\>python xor_cracker.py
0 OF04`
1 g|7#
3 ZlLLT
6 "XQ;)
8 <titl
9 U%w/;
14 1_5#T
17 MX's^
21 stein
[ ... snip ... ]
318 "XQ`"
320 </bod
324 I+B!v
[ ... snip ... ]
C:\>
We start noticing some HTML code at positions 8 and 320. At position 21 we also get 'stein' which is part of 'Wolfensteiny' from the PHP script. Using this information we can find the rest of the partial key which turns out to be '3Oiwa_o3@a-.m'. Using the same method we get the other 2 partial keys: 't_rsaat_4froc' and 'hx__ayowkleno'
Combining the 3 partial keys we get: th3_xOr_is_waaaay_too_w34k@flare-on.com
Sunday 5 November 2017
CTF Writeup - Flare-On 2017 - 08: flair.apk
- Name - flair.apk
- Category - Reverse Engineering
- Points - 1
- Binary - Download here
<?xml version="1.0" encoding="utf-8"?> <manifest package="com.flare_on.flair" platformBuildVersionCode="25" platformBuildVersionName="7.1.1" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="25" /> <meta-data android:name="android.support.VERSION" android:value="25.3.1" /> <application android:allowBackup="true" android:icon="@mipmap/flair_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.flare_on.flair.Chotchkies" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:exported="false" android:label="@string/michael_title" android:name="com.flare_on.flair.Michael" android:screenOrientation="portrait" /> <activity android:exported="false" android:label="@string/brian_title" android:name="com.flare_on.flair.Brian" android:screenOrientation="portrait" /> <activity android:exported="false" android:label="@string/milton_title" android:name="com.flare_on.flair.Milton" android:screenOrientation="portrait" /> <activity android:exported="false" android:label="@string/printer_title" android:name="com.flare_on.flair.Printer" android:screenOrientation="portrait" /> <meta-data android:name="vdf" android:value="cov" /> </application> </manifest>The manifest shows the main class that is called when the application is launched: 'com.flare_on.flair.Chotchkies'. This class reveals that we have 4 hurdles to overcome before we are presented with the key:
private void getFlair(int arg3) { Intent v0 = null; switch(arg3) { case 0: { v0 = new Intent(((Context)this), Michael.class); break; } case 1: { v0 = new Intent(((Context)this), Brian.class); break; } case 2: { v0 = new Intent(((Context)this), Milton.class); break; } case 3: { v0 = new Intent(((Context)this), Printer.class); break; } } this.startActivityForResult(v0, arg3); }So let's get started.
Michael
private boolean checkPassword(String arg7) { boolean v0; int v5 = 9; int v4 = 7; int v3 = 5; if((arg7.isEmpty()) || arg7.length() != 12) { v0 = false; } else { v0 = true; if(!arg7.startsWith("M")) { v0 = false; } if(arg7.indexOf(89) != 1) { v0 = false; } if(!arg7.substring(2, v3).equals("PRS")) { v0 = false; } if(arg7.codePointAt(v3) != 72 || arg7.codePointAt(6) != 69) { v0 = false; } if(arg7.charAt(v4) != arg7.charAt(8) || arg7.substring(v4, v5).hashCode() != 3040) { v0 = false; } if(arg7.indexOf("FT") != v5) { v0 = false; } if(arg7.lastIndexOf(87) == arg7.length() - 1) { return v0; } v0 = false; } return v0; }The only string that will make it through all the if statements is 'MYPRSHE__FTW'
Brian
[... snip ...] public void onClick(View arg5) { switch(arg5.getId()) { case 2131427424: { String v2 = this.q.getText().toString(); if(this.teraljdknh(v2, this.asdjfnhaxshcvhuw(this.findViewById(2131427422), this.findViewById(2131427423)))) { Util.flairSuccess(((Activity)this), v2); } else { Util.flairSadness(((Activity)this), this.tEr); ++this.tEr; } break; } } } [... snip ...] private boolean teraljdknh(String arg2, String arg3) { return arg2.equals(arg3); }If this.teraljdknh() returns True, then we go to Util.flairSuccess() otherwise we branch to Util.flairSadness(). Since teraljdknh() is just a string equals function, we can put a breakpoint when it is called and peek at the parameters: As soon as we hit the breakpoint, JEB shows us the local parameters and their values. The ones we're interested in are v2, which contains our input, and v3, which contains the expected input, as these are passed to teraljdknh(). The default type for local variables is 'int' so make sure to change it to 'string' to view the result. The expected input for this puzzle is therefore 'hashtag_covfefe_Fajitas!'
Milton
[ ... snip ... ] public void onRatingChanged(RatingBar arg6, float arg7, boolean arg8) { if(arg8) { if((((double)arg7)) == 4) { Milton.this.hild = Milton.this.hild + Stapler.vutfs("JP+98sTB4Zt6q8g=", 56, "State"); Milton.this.hild = Milton.this.hild + Stapler.vutfs("rh6HkuflHmw5Rw==", 96, "Chile"); Milton.this.hild = Milton.this.hild + Stapler.vutfs("+BNtTP/6", 118, "eagle"); Milton.this.hild = Milton.this.hild + Stapler.vutfs("oLLoI7X/jIp2+w==", 33, "wind"); Milton.this.hild = Milton.this.hild + Stapler.vutfs("w/MCnPD68xfjSCE=", 148, "river"); Milton.this.r.setEnabled(true); } arg6.setEnabled(false); } } [ ... snip ... ] private boolean breop(String arg5) { boolean v2 = false; if(!Milton.trsbd(((Context)this))) { byte[] v1 = Stapler.neapucx(arg5); try { v2 = Arrays.equals(v1, this.nbsadf()); } catch(NoSuchAlgorithmException v0) { v0.printStackTrace(); } catch(UnsupportedEncodingException v0_1) { v0_1.printStackTrace(); } } return v2; } [ ... snip ... ] public void onClick(View arg3) { switch(arg3.getId()) { case 2131427439: { String v0 = this.pexu.getText().toString(); if(this.breop(v0)) { Util.flairSuccess(((Activity)this), v0); } else { this.vbdrt(); Util.flairSadness(((Activity)this), this.rtgb); ++this.rtgb; } break; } } } [ ... snip ... ]The onRatingChanged() function is called when the rating on the 5-star scale has been selected. If the rating is 4 (line 5), the button is enabled (line 11). Now we can continue as usual. In the onClick() function, if this.breop() returns True, then we go to Util.flairSuccess() otherwise we branch to Util.flairSadness(). To ensure that breop() returns True, Array.equals() has to return True as well. Let's put a breakpoint on Array.equals(), input '123456' and hit the button: The function Array.equals() is comparing v1 and v3. I had to manually change the type to [B to get them to display an array of bytes. The value of v1 in the extra column is [0x12, 0x34, 0x56] which is our input so v3 must hold the required input. Disregarding the sign of the byte we get: '10AEA594831E0B42B956C578EF9A6D44EE39938D'
Printer
private boolean cgHbC(String arg15) { try { if(Printer.ksdc(this)) { boolean v9 = false; return v9; } Object v2 = this.wJPBw(Stapler.iemm("Gv@H")); Class v4 = Class.forName(Stapler.iemm(",e}e8yGS!8Dev)-e@")); int v5 = v4.getMethod(Stapler.iemm("vSBH"), null).invoke(v2, null).intValue(); byte[] v6 = new byte[v5]; Method v7 = v4.getMethod(Stapler.iemm("LHG"), Object.class); short v0; for(v0 = 0; v0 < v5; v0 = ((short)(v0 + 1))) { v6[v0] = Byte.class.cast(v7.invoke(v2, Short.valueOf(v0))).byteValue(); } return Class.forName(Stapler.iemm(",e}e8yGS!81PPe(v")).getMethod(Stapler.iemm("H?ye!v"), byte[].class, byte[].class).invoke(null, Stapler.neapucx(arg15), Stapler.poserw(v6)).booleanValue(); } catch(Exception v1) { v1.printStackTrace(); return false; } }The function is highly obfuscated. Notice though that arg15, which is our input, is only used on line 18. If we put a breakpoint at the end of function Stapler.poserw() to try and make sense of this line of code we get:
return Class.forName("java.util.Arrays").getMethod("equals"), byte[].class, byte[].class).invoke(null, Stapler.neapucx(arg15), Stapler.poserw(v6)).booleanValue();So this is a simple Array comparison between our input, arg15, and Stapler.poserw(v6). Putting a breakpoint at the end of Stapler.poserw(v6), changing the type of v1 to [B and collecting the hex values from the Extra column as we did before we get '5F1BE3C9B081C40DDFC4A0238156008EE71E24A4'.
Piecing It Together
- MYPRSHE__FTW
- hashtag_covfefe_Fajitas!
- 10AEA594831E0B42B956C578EF9A6D44EE39938D
- 5F1BE3C9B081C40DDFC4A0238156008EE71E24A4
Saturday 4 November 2017
CTF Writeup - Flare-On 2017 - 09: remorse.ino.hex
- Name - remorse.ino.hex
- Category - Reverse Engineering
- Points - 1
- Binary - Download here
:100000000C9462000C948A000C948A000C948A0070 :100010000C948A000C948A000C948A000C948A0038 :100020000C948A000C948A000C948A000C948A0028 :100030000C948A000C948A000C948A000C948A0018 [ ... snip ... ]After some research I found that this I was dealing with an Intel hex file for an Arduino UNO, i.e. an ATmega328P microcontroller. From here I've tried several methods to run the file such as using Atmel Studio or simavr but I couldn't find ways of interacting with the running program. I was even contemplating running it on a real Arduino. At the end I decided to load it in IDA and try my luck using static analysis. To load it properly in IDA, open the file, select ATMEL AVR and then select ATmega323_L. IDA nicely annotates some of the registers and pins for us too. After some time looking at the functions, sub_536() in particular caught my eye. The following are 2 screenshots of the entire function: Notice the comparison with the character '@' in small red basic block. This hinted that we were probably dealing with an email address, and hence the key. Before we can understand what is happened, we need to learn a few AVR instructions. The following are the necessary ones to understand what is happening in the green and yellow basic blocks:
- LD - Load Indirect from Data Space to Register
- LDI - Load Immediate
- ST - Store Indirect From Register to Data Space
- STD - Store Indirect From Register to Data Space
- EOR - Exclusive OR
- SUBI - Subtract Immediate
- CPI - Compare with Immediate
encrypted = [0xb5,0xb5,0x86,0xb4,0xf4,0xb3,0xf1,0xb0,0xb0,0xf1,0xed,0x80,0xbb,0x8f,0xbf,0x8d,0xc6,0x85,0x87,0xc0,0x94,0x81,0x8c] for y in range (255): answer = '' for x in range(len(encrypted)): answer += chr(((encrypted[x] ^ y) + x) % 256) if answer[10] == '@': print answer breakRunning the script:
Command Prompt
C:\>python solution.py
no_r3m0rs3@flare-on.com
C:\>
Subscribe to:
Posts (Atom)