ksnctf 15 Jewel
ksnctfの15問目を解いていきます。Javaと暗号の問題です。
アプリのインストールと実行
androidでapkファイルをダウンロードして実行すると「Your device is not supported」と表示されてアプリが終了します。でも機種の問題ではなさそう。
apkファイルの解析
androidファイルを解析するのは比較的簡単で逆コンパイラを使えばJavaのコードにまで復元してくれます。詳しいやり方は以下から↓
Android apk の解析 - Qiita
dex2jarは.apkを.jarに、jadは.classを.javaに変換します。kali linuxには2つともデフォルトでインストールされているのでそれを利用します。
>dex2jar Jewel.apk >unzip Jewel_dex2jar.jar -d ./Classes >jad -p JewelActivity.class > JewelActivity.java
ここからはソースコードを読んで解析していきます。アセンブリ読むより100倍マシですね。
// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.geocities.com/kpdus/jad.html // Decompiler options: packimports(3) package info.sweetduet.ksnctf.jewel; import android.app.Activity; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.os.Bundle; import android.telephony.TelephonyManager; import android.widget.ImageView; import android.widget.Toast; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; // Referenced classes of package info.sweetduet.ksnctf.jewel: // b, a public class JewelActivity extends Activity { public JewelActivity() { } public void onCreate(Bundle bundle) { String s; super.onCreate(bundle); setContentView(0x7f030000); s = ((TelephonyManager)getSystemService("phone")).getDeviceId(); String s1; try { MessageDigest messagedigest = MessageDigest.getInstance("SHA-256"); messagedigest.update(s.getBytes("ASCII")); s1 = (new BigInteger(messagedigest.digest())).toString(16); if(!s.substring(0, 8).equals("99999991")) { (new android.app.AlertDialog.Builder(this)).setMessage("Your device is not supported").setCancelable(false).setPositiveButton("OK", new b(this)).show(); return; } } catch(Exception exception) { Toast.makeText(this, exception.toString(), 1).show(); return; } if(!s1.equals("356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5")) { (new android.app.AlertDialog.Builder(this)).setMessage("You are not a valid user").setCancelable(false).setPositiveButton("OK", new a(this)).show(); return; } InputStream inputstream = getResources().openRawResource(0x7f040000); byte abyte0[] = new byte[inputstream.available()]; inputstream.read(abyte0); SecretKeySpec secretkeyspec = new SecretKeySpec((new StringBuilder("!")).append(s).toString().getBytes("ASCII"), "AES"); IvParameterSpec ivparameterspec = new IvParameterSpec("kLwC29iMc4nRMuE5".getBytes()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(2, secretkeyspec, ivparameterspec); byte abyte1[] = cipher.doFinal(abyte0); ImageView imageview = new ImageView(this); imageview.setImageBitmap(BitmapFactory.decodeByteArray(abyte1, 0, abyte1.length)); setContentView(imageview); return; } }
前半部分(~58行目、 InputStream inputstream = getResources().openRawResource(0x7f040000);以前)はアンドロイド端末のデバイスID(IMEI)前半が「99999991」で、ID全体をSHA256でハッシュ化した結果が「356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5」でなければブログラムを終了するというもの。
SHA256は不可逆関数で逆算できませんが、IMEIは15桁の数字なので、総当たりで求めます。
#!/usr/bin/env python import hashlib deviceid = 999999910000000 for x in range(0, 10000000): text = str((deviceid+x)) hash=hashlib.sha256(text).hexdigest() if hash == "356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5": print text break
プログラムからデバイスIDは「999999913371337」であることが分かりました。後半はこのデバイスIDを使って暗号化処理を行っています。
InputStream inputstream = getResources().openRawResource(0x7f040000); byte abyte0[] = new byte[inputstream.available()]; inputstream.read(abyte0);
リソースID(0x7f040000)から何らかのストリームを取得して内容をabyte0に格納しています。リソースIDとはアプリに置かれたリソースファイルを参照するために割り振られる数字のことです。リソースIDが何を指すのかを調べるには.apkの中にある.arscファイルを解析します。
unzip Jewel.apk aapt dump --values resources Jewel.apk resource.arsc > v.txt
v.txtにリソースIDの詳細が出力されます。
resource 0x7f040000 info.sweetduet.ksnctf.jewel:raw/jewel_c: t=0x03 d=0x00000004 (s=0x0008 r=0x00) (string16) "res/raw/jewel_c.png"
ID0x7f04000はjewel_c.pngを指しているようです。jewel_c.pngは/res/raw内にあります。jewel_c.pngはそのままでは画像として表示できないので、次の処理で復号処理を行っていると思われます。
SecretKeySpec secretkeyspec = new SecretKeySpec((new StringBuilder("!")).append(s).toString().getBytes("ASCII"), "AES"); IvParameterSpec ivparameterspec = new IvParameterSpec("kLwC29iMc4nRMuE5".getBytes());
AES暗号の暗号鍵を作成し、初期化ベクトルを指定しています。初期ベクトルは「kLwC29iMc4nRMuE5」暗号鍵は「!」と「デバイスID(999999913371337)」を組み合わせたものです。
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(2, secretkeyspec, ivparameterspec); byte abyte1[] = cipher.doFinal(abyte0);
AES暗号のCBCモード、PKCS5でjewel_c.pngの復号を実行しています。
jewel_c.pngの復号
メカニズムが分かったのでコードをかいてjewel_c.pngを復号します。コードは適当です。
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Main { public static void main(String[] args) throws NoSuchAlgorithmException, IOException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { //jewel_c.pngの読み込み FileInputStream fs = new FileInputStream("jewel_c.png"); byte abyte0[] = new byte[fs.available()]; fs.read(abyte0); //jewel_c.pngの復号 String s = "999999913371337"; SecretKeySpec secretkeyspec = new SecretKeySpec((new StringBuilder("!")).append(s).toString().getBytes("ASCII"), "AES"); IvParameterSpec ivparameterspec = new IvParameterSpec("kLwC29iMc4nRMuE5".getBytes()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(2, secretkeyspec, ivparameterspec); byte abyte1[] = cipher.doFinal(abyte0); //復号したデータの書き込み FileOutputStream fileOutStm = new FileOutputStream("jewel.png"); fileOutStm.write(abyte1); } }
↑で出力したファイルを開くと、
ちゃんと復元されていました。