NeoQuest 2017:「リーダーの修理!」でAndroidアプリケーションを反転します





今日、3月10日、 NeoQuest 2017のオンラインステージ終了しました。 審査員が結果を要約し、決勝への招待状を送信している間、評価表から判断すると、85ポイントまで獲得できるタスクの1つであるGreenoidのRaitapに慣れることをお勧めします。



いつものように、タスクはしばらくの間利用可能であり、時間がなかった人は落ち着いて仕事を終えたり、慣れることができます。



始めましょう



ファイルNeoQuest.apkをダウンロードし、逆コンパイル後にリストを取得します。



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"); } } }
      
      







コードにはコメントが付いていたので、説明する価値はないと思います。 次のステップに進みます。



1.dexの解読



まず、 APKファイルを解凍する必要があります。



 $ apktool d NeoQuest.apk
      
      





さまざまなアーキテクチャ向けのライブラリがいくつかあります。 IDAでそれらの1つを開きましょう。 復号化を担当するコードは次のようになります。







そして、そのためのJavaラッパー:





ご覧のとおり、有効なIMEIがあり 、次にいくつかのオプションがあります。



  1. smaliファイル内の対応する行を置き換えることでapkファイル自体にパッチを適用し、正しいIMEIを送信して復号化できます。
  2. または、別の言語に書き換えて、すべてを手動で行います。


最初のオプションはよりシンプルで、2番目のオプションはより便利です。電話画面からキーを書き換える必要はありませんが、コンソールからキーをコピーするだけです。 Pythonでは、次のようになります。



 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コードは完璧ではなく、最適化することもできますが、私の意見ではこのオプションはより視覚的です。



開始後、復号化されたファイルout.dexを取得します。これは、次のコードに逆コンパイルされます。



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(); } }
      
      







わかった 割り当ての最後の部分に進むことができます。



サーバーへのデータの送信



credファイルの内容は次のとおりです。



信用する
管理者

26892263f3d18dfabb665e2d2a680899b2577f0f4daa7728​​7fadb3e4ae581ec1

NeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuest




まずログイン、次にキーとコメントが来ます。



そのまま送信すると、ログインが既に行われていることを示すメッセージが表示されます。



ログインを変更すると、サーバーは間違ったCRC32署名を誓います。



元の署名と変更されたデータを送信すると、サーバーは署名が一致しないことを報告します。



これは、チェックサム計算アルゴリズムがIDAでどのように見えるかです。







上記に基づいて、元の署名に対応するデータを送信する必要がありますが、正しいログインが必要です。 CRC32のブロックは4バイトのみを使用し、署名はcredファイルの内容全体に基づいて計算されるため、これらの4バイトのブロックを解除する必要があります。



 #!/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
      
      





メッセージ全体のメッセージの署名を計算しないために、選択したセクションについて事前に計算してから、残りの4バイトをカウントするだけです。 開始し、しばらくすると答えが得られます。



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





これをサーバーに送信し、フラグを取得します。



 #!/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()
      
      





データを送信すると、答えが得られます。

ログイン-OK

key_word-OK

CRC32-OK



ce91ecbefd83b69a88055e151800f4ebec7cda1a93b94c​​b0b420251a169e5abf



それだけです!



All Articles