
今日、3月10日、
NeoQuest 2017のオンラインステージ
は終了しました。 審査員が結果を要約し、決勝への招待状を送信している間、評価表から判断すると、85ポイントまで獲得できるタスクの1つであるGreenoidのRaitapに慣れることをお勧めします。
いつものように、タスクはしばらくの間利用可能であり、時間がなかった人は落ち着いて仕事を終えたり、慣れることができます。
始めましょう
ファイル
NeoQuest.apkをダウンロードし、逆コンパイル後にリストを取得します。
MainActivity.javapackage 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");
コードにはコメントが付いていたので、説明する価値はないと思います。 次のステップに進みます。
1.dexの解読
まず、
APKファイルを解凍する必要があります。
$ apktool d NeoQuest.apk
さまざまなアーキテクチャ向けのライブラリがいくつかあります。
IDAでそれらの1つを開きましょう。 復号化を担当するコードは次のようになります。

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

ご覧のとおり、有効な
IMEIがあり 、次にいくつかのオプションがあります。
- smaliファイル内の対応する行を置き換えることでapkファイル自体にパッチを適用し、正しいIMEIを送信して復号化できます。
- または、別の言語に書き換えて、すべてを手動で行います。
最初のオプションはよりシンプルで、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"; static final class C00001 extends AsyncTask<Void, Void, String> { final String val$comment; final String val$crc32; final String val$keyWorld; final 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ファイルの内容は次のとおりです。
信用する管理者
26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1
NeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuest
まずログイン、次にキーとコメントが来ます。
そのまま送信すると、ログインが既に行われていることを示すメッセージが表示されます。
ログインを変更すると、サーバーは間違った
CRC32署名を誓います。
元の署名と変更されたデータを送信すると、サーバーは署名が一致しないことを報告します。
これは、チェックサム計算アルゴリズムがIDAでどのように見えるかです。

上記に基づいて、元の署名に対応するデータを送信する必要がありますが、正しいログインが必要です。
CRC32のブロックは4バイトのみを使用し、署名は
credファイルの内容全体に基づいて計算されるため、これらの4バイトのブロックを解除する必要があります。
メッセージ全体のメッセージの署名を計算しないために、選択したセクションについて事前に計算してから、残りの4バイトをカウントするだけです。 開始し、しばらくすると答えが得られます。
$ ./libneo.py CRC Found: b'AdminAdmin\r\n26892263f3d18dfabb665e2d2a680899b2577f0f4daa77287fadb3e4ae581ec1\r\nNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeoQuestNeo\xfe\xa3\x0f#'
これをサーバーに送信し、フラグを取得します。
データを送信すると、答えが得られます。
ログイン-OK
key_word-OK
CRC32-OK
ce91ecbefd83b69a88055e151800f4ebec7cda1a93b94cb0b420251a169e5abf
それだけです!