📜 ⬆️ ⬇️

NeoQuest 2017: Reverse android apps in the task “Repair the Leader!”



Good day to all, today, March 10, the online stage NeoQuest 2017 ended. While the jury is summing up the results and sending out invitations to the final, I suggest that you familiarize yourself with one of the assignments: Greenoid for which judging by the rating table, you could get up to 85 points.

As usual, the tasks will be available for some time, but those who did not have time, can now calmly complete or review.

Let's start


Download the NeoQuest.apk file and after decompiling we get the listing:
')
MainActivity.java
package com.neobit.neoquest; import android.app.Activity; import android.content.res.AssetManager; import android.os.Bundle; import android.telephony.TelephonyManager; import android.util.Base64; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; import dalvik.system.DexClassLoader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Arrays; public class MainActivity extends Activity implements OnClickListener { private Method f1373a; static { System.loadLibrary("neolib"); //   so  } //      public native byte[] decrypt(String str, byte[] bArr); public native int nativeCRC32sum(byte[] bArr); public void onClick(View view) { int i = 0; CharSequence charSequence = ""; try { InputStream open = getAssets().open("cred"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] bArr = new byte[1024]; //    cred while (true) { int read = open.read(bArr, 0, 1024); if (read == -1) { break; } byteArrayOutputStream.write(bArr, 0, read); } byteArrayOutputStream.flush(); byte[] toByteArray = byteArrayOutputStream.toByteArray(); byteArrayOutputStream.close(); open.close(); while (i < 1024 && bArr[i] != (byte) 10) { i++; } //      String login = new String(toByteArray, 0, i - 1, "UTF-8"); int i2 = i + 1; i = i2; while (i < toByteArray.length && bArr[i] != (byte) 10) { i++; } String key = new String(toByteArray, i2, (i - i2) - 1, "UTF-8"); String comment = Base64.encodeToString(Arrays.copyOfRange(toByteArray, i + 1, toByteArray.length), 2); byteArrayOutputStream.close(); //  CRC32     cred String crc32 = Integer.toHexString(nativeCRC32sum(toByteArray)).toUpperCase(); //     charSequence = (String) this.f1373a.invoke(null, new Object[]{login, key, comment, crc32}); } catch (Exception e) { } ((TextView) findViewById(2131492970)).setText(charSequence); } protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(2130968601); AssetManager assets = getAssets(); try { //   IMEI  String deviceId = ((TelephonyManager) getSystemService("phone")).getDeviceId(); //    1.dex InputStream open = assets.open("1.dex"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] bArr = new byte[1024]; while (true) { int read = open.read(bArr); if (read != -1) { byteArrayOutputStream.write(bArr, 0, read); } else { //  1.dex byte[] decrypt = decrypt(deviceId, byteArrayOutputStream.toByteArray()); File file = new File(getCacheDir(), "1.dex"); file.delete(); FileOutputStream fileOutputStream = new FileOutputStream(file, false); fileOutputStream.write(decrypt); fileOutputStream.close(); //   get   com.neobit.neoquest.Server this.f1373a = new DexClassLoader(file.getAbsolutePath(), getDir("outdex", 0).getAbsolutePath(), null, getClassLoader()).loadClass("com.neobit.neoquest.Server").getMethod("get", new Class[]{String.class, String.class, String.class, String.class}); findViewById(2131492969).setOnClickListener(this); return; } } } catch (Throwable th) { //      IMEI ((TextView) findViewById(2131492970)).setText("Phone IMEI is not correct"); } } } 


The code was supplied with comments, so I think you should not explain it. Go to the next step.

Decrypt 1.dex


First you need to unzip the APK file:

 $ apktool d NeoQuest.apk 

We find there several libraries for different architectures. Let's open one of them in IDA . The code that is responsible for decoding looks like this:



And Java wrapper for it:


As you can see, there is a correct IMEI here, then there are several options:

  1. You can patch the apk file itself, replacing the corresponding lines in the smali file, so that the correct IMEI is sent to decrypt ;
  2. Or rewrite it into another language and do everything manually.

The first option is simpler, and the second is more convenient, because then you don’t have to rewrite the key from the phone’s screen, but you can just copy it from the console. In Python, it will look like this:

 def getLbits(number): bits = '%08x' % number return int(bits[-2:], 16) def setLbits(dst, src): bits = '%08x' % src bits = int(bits[-2:], 16) dst = '%08x' % dst return int('%s%02x' % (dst[:-2], bits), 16) def decrypt(data, data_len, key, key_len): prekey = {} prekey2 = {} for i in range(0x100): prekey[i] = i prekey2[i] = ord(key[i % key_len]) y = 0x0 for i in range(0x100): rdi = prekey[i] key_len = setLbits(key_len, prekey[i] + prekey2[i] + y) y = key_len prekey[i] = getLbits(getLbits(prekey[key_len]) & 0xFF) prekey[key_len] = getLbits(rdi) result = [] if data_len != 0x0: i = 0x0 y = 0x0 k = 0x0 while i < data_len: k = (k + 0x1) & 0xFF rax = getLbits(prekey[k]) y = (y + rax) & 0xFF prekey[k] = getLbits(prekey[y]) prekey[y] = rax rax += prekey[k] result.append(data[i] ^ getLbits(prekey[getLbits(rax)])) i += 0x1 return result dex = open('1.dex', 'rb').read() imei = '352612062282062' result = decrypt(dex, len(dex), imei, len(imei)) outdex = open('out.dex', 'wb') outdex.write(bytes(result)) outdex.close() 

PS The code is not perfect and can be optimized, but in my opinion this option is more visual.

After launching, we get the decoded out.dex file, which is decompiled into the following code:

Server.java
 package com.neobit.neoquest; import android.os.AsyncTask; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.ExecutionException; public class Server { private static final String address = "http://213.170.100.214/neoquest.php"; /* renamed from: com.neobit.neoquest.Server.1 */ static final class C00001 extends AsyncTask<Void, Void, String> { final /* synthetic */ String val$comment; final /* synthetic */ String val$crc32; final /* synthetic */ String val$keyWorld; final /* synthetic */ String val$login; C00001(String str, String str2, String str3, String str4) { this.val$login = str; this.val$keyWorld = str2; this.val$comment = str3; this.val$crc32 = str4; } protected String doInBackground(Void... voidArr) { try { HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(Server.address).openConnection(); httpURLConnection.setRequestMethod("POST"); httpURLConnection.addRequestProperty("Content-Type", "application/json"); DataOutputStream dataOutputStream = new DataOutputStream(httpURLConnection.getOutputStream()); dataOutputStream.writeBytes(String.format("{\"login\":\"%s\",\"key_word\":\"%s\",\"comment\":\"%s\",\"crc32\":\"%s\"}", new Object[]{this.val$login, this.val$keyWorld, this.val$comment, this.val$crc32})); dataOutputStream.flush(); dataOutputStream.close(); InputStream inputStream = httpURLConnection.getInputStream(); String access$000 = Server.isToString(inputStream); inputStream.close(); httpURLConnection.disconnect(); return access$000; } catch (Exception e) { e.printStackTrace(); return ""; } } } public static String get(String str, String str2, String str3, String str4) throws ExecutionException, InterruptedException { return (String) new C00001(str, str2, str3, str4).execute(new Void[0]).get(); } private static String isToString(InputStream inputStream) throws IOException { BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); for (int read = bufferedInputStream.read(); read != -1; read = bufferedInputStream.read()) { byteArrayOutputStream.write((byte) read); } return byteArrayOutputStream.toString(); } } 


Okay! You can proceed to the last part of the task.

Sending data to the server


The following is the contents of the cred file:

cred
Admin
26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1
NeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuest


First comes the login, then the key and comment.

If you send it as it is, you will receive a message that the login is already taken.

If you change the login, the server swears on an incorrect CRC32 signature.

If you send the original signature and the modified data, the server reports that the signature does not match.

This is how IDA checksum algorithm looks like in IDA:



Based on the foregoing, it follows that you need to send such data that will match the original signature, but with the correct login. Since the block in CRC32 occupies only 4 bytes, and the signature is calculated on the basis of the entire contents of the cred file, you just need to clear these 4 bytes:

 #!/usr/bin/python3 from struct import pack crc_tab = [ 0, 0x2BDDD04F, 0x57BBA09E, 0x7C6670D1, 0x0AF77413C, 0x84AA9173, 0x0F8CCE1A2, 0x0D31131ED, 0x0F6DD1A53, 0x0DD00CA1C, 0x0A166BACD, 0x8ABB6A82, 0x59AA5B6F, 0x72778B20, 0x0E11FBF1, 0x25CC2BBE, 0x4589AC8D, 0x6E547CC2, 0x12320C13, 0x39EFDC5C, 0x0EAFEEDB1, 0x0C1233DFE, 0x0BD454D2F, 0x96989D60, 0x0B354B6DE, 0x98896691, 0x0E4EF1640, 0x0CF32C60F, 0x1C23F7E2, 0x37FE27AD, 0x4B98577C, 0x60458733, 0x8B13591A, 0x0A0CE8955, 0x0DCA8F984, 0x0F77529CB, 0x24641826, 0x0FB9C869, 0x73DFB8B8, 0x580268F7, 0x7DCE4349, 0x56139306, 0x2A75E3D7, 0x1A83398, 0x0D2B90275, 0x0F964D23A, 0x8502A2EB, 0x0AEDF72A4, 0x0CE9AF597, 0x0E54725D8, 0x99215509, 0x0B2FC8546, 0x61EDB4AB, 0x4A3064E4, 0x36561435, 0x1D8BC47A, 0x3847EFC4, 0x139A3F8B, 0x6FFC4F5A, 0x44219F15, 0x9730AEF8, 0x0BCED7EB7, 0x0C08B0E66, 0x0EB56DE29, 0x0BE152A1F, 0x95C8FA50, 0x0E9AE8A81, 0x0C2735ACE, 0x11626B23, 0x3ABFBB6C, 0x46D9CBBD, 0x6D041BF2, 0x48C8304C, 0x6315E003, 0x1F7390D2, 0x34AE409D, 0x0E7BF7170, 0x0CC62A13F, 0x0B004D1EE, 0x9BD901A1, 0x0FB9C8692, 0x0D04156DD, 0x0AC27260C, 0x87FAF643, 0x54EBC7AE, 0x7F3617E1, 0x3506730, 0x288DB77F, 0x0D419CC1, 0x269C4C8E, 0x5AFA3C5F, 0x7127EC10, 0x0A236DDFD, 0x89EB0DB2, 0x0F58D7D63, 0x0DE50AD2C, 0x35067305, 0x1EDBA34A, 0x62BDD39B, 0x496003D4, 0x9A713239, 0x0B1ACE276, 0x0CDCA92A7, 0x0E61742E8, 0x0C3DB6956, 0x0E806B919, 0x9460C9C8, 0x0BFBD1987, 0x6CAC286A, 0x4771F825, 0x3B1788F4, 0x10CA58BB, 0x708FDF88, 0x5B520FC7, 0x27347F16, 0x0CE9AF59, 0x0DFF89EB4, 0x0F4254EFB, 0x88433E2A, 0x0A39EEE65, 0x8652C5DB, 0x0AD8F1594, 0x0D1E96545, 0x0FA34B50A, 0x292584E7, 0x2F854A8, 0x7E9E2479, 0x5543F436, 0x0D419CC15, 0x0FFC41C5A, 0x83A26C8B, 0x0A87FBCC4, 0x7B6E8D29, 0x50B35D66, 0x2CD52DB7, 0x708FDF8, 0x22C4D646, 0x9190609, 0x757F76D8, 0x5EA2A697, 0x8DB3977A, 0x0A66E4735, 0x0DA0837E4, 0x0F1D5E7AB, 0x91906098, 0x0BA4DB0D7, 0x0C62BC006, 0x0EDF61049, 0x3EE721A4, 0x153AF1EB, 0x695C813A, 0x42815175, 0x674D7ACB, 0x4C90AA84, 0x30F6DA55, 0x1B2B0A1A, 0x0C83A3BF7, 0x0E3E7EBB8, 0x9F819B69, 0x0B45C4B26, 0x5F0A950F, 0x74D74540, 0x8B13591, 0x236CE5DE, 0x0F07DD433, 0x0DBA0047C, 0x0A7C674AD, 0x8C1BA4E2, 0x0A9D78F5C, 0x820A5F13, 0x0FE6C2FC2, 0x0D5B1FF8D, 0x6A0CE60, 0x2D7D1E2F, 0x511B6EFE, 0x7AC6BEB1, 0x1A833982, 0x315EE9CD, 0x4D38991C, 0x66E54953, 0x0B5F478BE, 0x9E29A8F1, 0x0E24FD820, 0x0C992086F, 0x0EC5E23D1, 0x0C783F39E, 0x0BBE5834F, 0x90385300, 0x432962ED, 0x68F4B2A2, 0x1492C273, 0x3F4F123C, 0x6A0CE60A, 0x41D13645, 0x3DB74694, 0x166A96DB, 0x0C57BA736, 0x0EEA67779, 0x92C007A8, 0x0B91DD7E7, 0x9CD1FC59, 0x0B70C2C16, 0x0CB6A5CC7, 0x0E0B78C88, 0x33A6BD65, 0x187B6D2A, 0x641D1DFB, 0x4FC0CDB4, 0x2F854A87, 0x4589AC8, 0x783EEA19, 0x53E33A56, 0x80F20BBB, 0x0AB2FDBF4, 0x0D749AB25, 0x0FC947B6A, 0x0D95850D4, 0x0F285809B, 0x8EE3F04A, 0x0A53E2005, 0x762F11E8, 0x5DF2C1A7, 0x2194B176, 0x0A496139, 0x0E11FBF10, 0x0CAC26F5F, 0x0B6A41F8E, 0x9D79CFC1, 0x4E68FE2C, 0x65B52E63, 0x19D35EB2, 0x320E8EFD, 0x17C2A543, 0x3C1F750C, 0x407905DD, 0x6BA4D592, 0x0B8B5E47F, 0x93683430, 0x0EF0E44E1, 0x0C4D394AE, 0x0A496139D, 0x8F4BC3D2, 0x0F32DB303, 0x0D8F0634C, 0x0BE152A1, 0x203C82EE, 0x5C5AF23F, 0x77872270, 0x524B09CE, 0x7996D981, 0x5F0A950, 0x2E2D791F, 0x0FD3C48F2, 0x0D6E198BD, 0x0AA87E86C, 0x815A3823 ] def crc32(array, array_len): v3 = 2910424328 #       for v4 in array[-4:]: v3 = (v3 >> 8) ^ crc_tab[getLbits(v3 ^ v4)] return NOT(v3) def checkCRC(item): if crc32(item, len(item)) == 0x3E9A75C2: print('CRC Found: %s' % item) creds = b'AdminAdmin\r\n26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1\r\nNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeo' x1 = 0xFFFFFFFF while x1 > 0: checkCRC(creds + pack('>I', x1)) x1 -= 1 r \ nNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeo ' #!/usr/bin/python3 from struct import pack crc_tab = [ 0, 0x2BDDD04F, 0x57BBA09E, 0x7C6670D1, 0x0AF77413C, 0x84AA9173, 0x0F8CCE1A2, 0x0D31131ED, 0x0F6DD1A53, 0x0DD00CA1C, 0x0A166BACD, 0x8ABB6A82, 0x59AA5B6F, 0x72778B20, 0x0E11FBF1, 0x25CC2BBE, 0x4589AC8D, 0x6E547CC2, 0x12320C13, 0x39EFDC5C, 0x0EAFEEDB1, 0x0C1233DFE, 0x0BD454D2F, 0x96989D60, 0x0B354B6DE, 0x98896691, 0x0E4EF1640, 0x0CF32C60F, 0x1C23F7E2, 0x37FE27AD, 0x4B98577C, 0x60458733, 0x8B13591A, 0x0A0CE8955, 0x0DCA8F984, 0x0F77529CB, 0x24641826, 0x0FB9C869, 0x73DFB8B8, 0x580268F7, 0x7DCE4349, 0x56139306, 0x2A75E3D7, 0x1A83398, 0x0D2B90275, 0x0F964D23A, 0x8502A2EB, 0x0AEDF72A4, 0x0CE9AF597, 0x0E54725D8, 0x99215509, 0x0B2FC8546, 0x61EDB4AB, 0x4A3064E4, 0x36561435, 0x1D8BC47A, 0x3847EFC4, 0x139A3F8B, 0x6FFC4F5A, 0x44219F15, 0x9730AEF8, 0x0BCED7EB7, 0x0C08B0E66, 0x0EB56DE29, 0x0BE152A1F, 0x95C8FA50, 0x0E9AE8A81, 0x0C2735ACE, 0x11626B23, 0x3ABFBB6C, 0x46D9CBBD, 0x6D041BF2, 0x48C8304C, 0x6315E003, 0x1F7390D2, 0x34AE409D, 0x0E7BF7170, 0x0CC62A13F, 0x0B004D1EE, 0x9BD901A1, 0x0FB9C8692, 0x0D04156DD, 0x0AC27260C, 0x87FAF643, 0x54EBC7AE, 0x7F3617E1, 0x3506730, 0x288DB77F, 0x0D419CC1, 0x269C4C8E, 0x5AFA3C5F, 0x7127EC10, 0x0A236DDFD, 0x89EB0DB2, 0x0F58D7D63, 0x0DE50AD2C, 0x35067305, 0x1EDBA34A, 0x62BDD39B, 0x496003D4, 0x9A713239, 0x0B1ACE276, 0x0CDCA92A7, 0x0E61742E8, 0x0C3DB6956, 0x0E806B919, 0x9460C9C8, 0x0BFBD1987, 0x6CAC286A, 0x4771F825, 0x3B1788F4, 0x10CA58BB, 0x708FDF88, 0x5B520FC7, 0x27347F16, 0x0CE9AF59, 0x0DFF89EB4, 0x0F4254EFB, 0x88433E2A, 0x0A39EEE65, 0x8652C5DB, 0x0AD8F1594, 0x0D1E96545, 0x0FA34B50A, 0x292584E7, 0x2F854A8, 0x7E9E2479, 0x5543F436, 0x0D419CC15, 0x0FFC41C5A, 0x83A26C8B, 0x0A87FBCC4, 0x7B6E8D29, 0x50B35D66, 0x2CD52DB7, 0x708FDF8, 0x22C4D646, 0x9190609, 0x757F76D8, 0x5EA2A697, 0x8DB3977A, 0x0A66E4735, 0x0DA0837E4, 0x0F1D5E7AB, 0x91906098, 0x0BA4DB0D7, 0x0C62BC006, 0x0EDF61049, 0x3EE721A4, 0x153AF1EB, 0x695C813A, 0x42815175, 0x674D7ACB, 0x4C90AA84, 0x30F6DA55, 0x1B2B0A1A, 0x0C83A3BF7, 0x0E3E7EBB8, 0x9F819B69, 0x0B45C4B26, 0x5F0A950F, 0x74D74540, 0x8B13591, 0x236CE5DE, 0x0F07DD433, 0x0DBA0047C, 0x0A7C674AD, 0x8C1BA4E2, 0x0A9D78F5C, 0x820A5F13, 0x0FE6C2FC2, 0x0D5B1FF8D, 0x6A0CE60, 0x2D7D1E2F, 0x511B6EFE, 0x7AC6BEB1, 0x1A833982, 0x315EE9CD, 0x4D38991C, 0x66E54953, 0x0B5F478BE, 0x9E29A8F1, 0x0E24FD820, 0x0C992086F, 0x0EC5E23D1, 0x0C783F39E, 0x0BBE5834F, 0x90385300, 0x432962ED, 0x68F4B2A2, 0x1492C273, 0x3F4F123C, 0x6A0CE60A, 0x41D13645, 0x3DB74694, 0x166A96DB, 0x0C57BA736, 0x0EEA67779, 0x92C007A8, 0x0B91DD7E7, 0x9CD1FC59, 0x0B70C2C16, 0x0CB6A5CC7, 0x0E0B78C88, 0x33A6BD65, 0x187B6D2A, 0x641D1DFB, 0x4FC0CDB4, 0x2F854A87, 0x4589AC8, 0x783EEA19, 0x53E33A56, 0x80F20BBB, 0x0AB2FDBF4, 0x0D749AB25, 0x0FC947B6A, 0x0D95850D4, 0x0F285809B, 0x8EE3F04A, 0x0A53E2005, 0x762F11E8, 0x5DF2C1A7, 0x2194B176, 0x0A496139, 0x0E11FBF10, 0x0CAC26F5F, 0x0B6A41F8E, 0x9D79CFC1, 0x4E68FE2C, 0x65B52E63, 0x19D35EB2, 0x320E8EFD, 0x17C2A543, 0x3C1F750C, 0x407905DD, 0x6BA4D592, 0x0B8B5E47F, 0x93683430, 0x0EF0E44E1, 0x0C4D394AE, 0x0A496139D, 0x8F4BC3D2, 0x0F32DB303, 0x0D8F0634C, 0x0BE152A1, 0x203C82EE, 0x5C5AF23F, 0x77872270, 0x524B09CE, 0x7996D981, 0x5F0A950, 0x2E2D791F, 0x0FD3C48F2, 0x0D6E198BD, 0x0AA87E86C, 0x815A3823 ] def crc32(array, array_len): v3 = 2910424328 #       for v4 in array[-4:]: v3 = (v3 >> 8) ^ crc_tab[getLbits(v3 ^ v4)] return NOT(v3) def checkCRC(item): if crc32(item, len(item)) == 0x3E9A75C2: print('CRC Found: %s' % item) creds = b'AdminAdmin\r\n26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1\r\nNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeo' x1 = 0xFFFFFFFF while x1 > 0: checkCRC(creds + pack('>I', x1)) x1 -= 1 

In order not to calculate the signature for the entire message, you can calculate it in advance for the selected area, and then just count the remaining 4 bytes. We start, and after a while we get the answer:

 $ ./libneo.py CRC Found: b'AdminAdmin\r\n26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1\r\nNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeo\xfe\xa3\x0f#' 

Now send it to the server and pick up the flag:

 #!/usr/bin/python3 import requests import base64 import json def connect(): url = 'http://213.170.100.214/neoquest.php' header = {'Content-Type': 'application/json'} data = {"comment": "", "login": "AdminAdmin", "crc32": "3E9A75C2", "key_word": "26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1"} data['comment'] = base64.b64encode(b'NeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeo\xfe\xa3\x0f#').decode() data = json.dumps(data) req = requests.post(url, data, header).text if 'wrong' not in req and 'not your checksum!' not in req: print(req) connect() 

After sending the data we get the answer:
login - OK
key_word - OK
CRC32 - OK

ce91ecbefd83b69a88055e151800f4ebec7cda1a93b94cb0b420251a169e5abf

That's all!

Source: https://habr.com/ru/post/323656/


All Articles