ChogeLog

主にセキュリティ関係の記録やWrite-up。たまーに日記も。

#16 Math I (write-up)

問題

ksnctf.sweetduet.info

冪剰余の計算問題に見えるが、実際はRSA暗号の復号を行う問題。
知っていればすぐに解ける問題だが、自分はRSA暗号について詳しく知らなかったため結構時間かかった…。

(対数に変換したりして頑張って解くのかなと思ったが、計算量が多すぎて不可能なことに後から気づく。。その後、冪剰余について調べてたらRSA暗号を見つけて解くことができた。)

解法

RSA暗号の平文mを求める問題。
暗号文を復号するには、まず秘密鍵を求める必要がある。通常、秘密鍵を入手することは困難だが、n=pqのpとqのそれぞれの値(素数)を知っていれば秘密鍵を求めることができる。今回はp, qの値も丁寧に用意されているので簡単に解くことが可能。

RSA暗号の仕組みについては以下のサイトを参考にした。ここではRSA暗号についての解説はしない。(というかできない)

qiita.com

qiita.com

上記サイトを参考にすると、秘密鍵dはd=e^(-1)%((p-1)*(q-1))で求めることができる。
秘密鍵を入手した後はm=c^d%nでm(平文)を求めることができる。
ということで、以下のようなプログラムで計算を行った。

N = 1517330236262917595314610888889322115651087080826711948897066340883208205571592392362650858571076247939805436226544833224526137582834770402681005343930059463684528957271778199162575053306238099823295117697031968370690372250916935800738698142103275969223264184374648246277564306900886005299731265812255274723175925185522344831066577166867786835955092059346244885587228196357297758371381557924676260190209536670230008561217008649261974735505203813478978893582292682827884118215872470401293272325715864815977064075988643101088355047954735427424641386870772845440782632933485165110172437511822736907550777817722248753671107339823410418938404382732079381329288400012929311347390423061254658780185245562668131009832293474920208834795460061115101364091252176594144096675899952570380792978037217747311595899301451192342027799533264325948876556110474850761538179748318187805312451895898751337975457949549497666542175077894987697085521882531938339334715190663665300179658557458036053188152532948734992896239950564081581184284728802682982779186068791931259198917308153082917381616147108543673346682338045309449569430550618884202465809290850964525390539782080230737593560891353558335337408957948041667929154230334506735825418239563481028126435029
C = 225549592628492616152632265482125315868911125659971085929712296366214355608049224179339757637982541542745010822022226409126123627804953064072055667012172681551500780763483172914389813057444669314726404135978565446282309019729994976815925850916487257699707478206132474710963752590399332920672607440793116387051071191919835316845827838287954541558777355864714782464299278036910958484272003656702623646042688124964364376687297742060363382322519436200343894901785951095760714894439233966409337996138592489997024933882003852590408577812535049335652212448474376457015077047529818315877549614859586475504070051201054704954654093482056493092930700787890579346065916834434739980791402216175555075896066616519150164831990626727591876115821219941268309678240872298029611746575376322733311657394502859852213595389607239431585120943268774679785316133478171225719729917877009624611286702010936951705160870997184123775488592130586606070277173392647225589257616518666852404878425355285270687131724258281902727717116041282358028398978152480549468694659695121115046850718180640407034795656480263573773381753855724693739080045739160297875306923958599742379878734638341856117533253251168244471273520476474579680250862738227337561115160603373096699944163
P = 34111525225922333955113751419357677129436029651245533697825114748126342624744832960936498161825269430327019858323450578875242014583535842110912370431931233957939950911741013017595977471949767235426490850284286661592357779825212265055931705799916913817655743434497422993498931394618832741336247426815710164342599150990608143637331068220244525541794855651643135012846039439355101027994945120698530177329829213208761057392236875366458197098507252851244132455996468628957560178868724310000317011912994632328371761486669358065577269198065792981537378448324923622959249447066754504943097391628716371245206444816309511381323
Q = 44481453884385518268018625442920628989497457642625668259648790876723318635861137128631112417617317160816537010595885992856520476731882382742220627466006460645416066646852266992087386855491152795237153901319521506429873434336969666536995399866125781057768075533560120399184566956433129854995464893265403724034960689938351450709950699740508459206785093693277541785285699733873530541918483842122691276322286810422297015782658645129421043160749040846216892671031156465364652681036828461619272427318758098538927727392459501761203842363017121432657534770898181975532066012149902177196510416802134121754859407938165610800223
E = 65537

s = ((P-1)*(Q-1))
d = pow(E, -1, s)
m = pow(C, d, N)
ans = format(m, "0512x")
print(bytes.fromhex(ans).decode())

上記プログラムを実行するとフラグをゲットできた。

所感

完全には理解していないが、n=pqを知っていればRSA暗号を復号可能なことは分かった。
これはほかのCTFでも使いそうな知識…

#15 Jewel (write-up)

問題

ksnctf.sweetduet.info

APKファイルが用意されている。

解法

APKファイルのデコンパイル

APKファイルなのでとりあえずエミュレータで動かそう…と思ったが、エラーが出て動かず。(そりゃそうか)
ということでまずはファイルの中身を見ていく。APKはZIP形式なので、ZIPを解凍すると以下のようなファイルが入っていた。

各ファイルを見ていくと、resディレクトリの中に怪しい画像ファイルが存在している。拡張子はpngだが、中身は画像ではなかった。

ほかのファイルに対してもstringsコマンドとかを使ってみたが、それだけではフラグを取れそうにはなかった。そのため、ここからはAPKファイルをデコンパイルリバースエンジニアリング)していく。
APKファイルのデコンパイルについてはこちらの記事を参考にした。

qiita.com

まずはdex2jarをダウンロードする。
その後、dex2jarを使用してclass.dexをjarファイルに変換する。

そのjarファイルをunzipすると、以下のように3つのclassファイルを確認できる。

次に、JADJava Decompiler)をダウンロードし、先ほど確認したファイルのうち JewelActivity.classデコンパイルする。

これでAPKファイルのデコンパイルが完了した。

コードの解析編

生成されたJewelActivity.jadの中身は以下のようになっている。(コメント箇所は追記している)

// 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)
    {
        //前半パート
        Object obj;
        super.onCreate(bundle);
        setContentView(0x7f030000);
        obj = ((TelephonyManager)getSystemService("phone")).getDeviceId(); //デバイスIDを取得
        try
        {
            bundle = MessageDigest.getInstance("SHA-256");
            bundle.update(((String) (obj)).getBytes("ASCII"));
            bundle = (new BigInteger(bundle.digest())).toString(16);
            if(!((String) (obj)).substring(0, 8).equals("99999991")) //デバイスIDの先頭8文字
            {
                (new android.app.AlertDialog.Builder(this)).setMessage("Your device is not supported").setCancelable(false).setPositiveButton("OK", new b(this)).show();
                return;
            }
        }
        // Misplaced declaration of an exception variable
        catch(Bundle bundle)
        {
            Toast.makeText(this, bundle.toString(), 1).show();
            return;
        }
        if(!bundle.equals("356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5")) //デバイスIDのSHA-256の結果
        {
            (new android.app.AlertDialog.Builder(this)).setMessage("You are not a valid user").setCancelable(false).setPositiveButton("OK", new a(this)).show();
            return;
        }
        
         //後半パート
        Object obj1 = getResources().openRawResource(0x7f040000); //ファイル読み込み(画像)
        bundle = new byte[((InputStream) (obj1)).available()];
        ((InputStream) (obj1)).read(bundle);
        obj = new SecretKeySpec((new StringBuilder("!")).append(((String) (obj))).toString().getBytes("ASCII"), "AES"); //秘密鍵の作成
        obj1 = new IvParameterSpec("kLwC29iMc4nRMuE5".getBytes()); //IVの作成
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //AES暗号、CBCブロック、PKCS#5パディング
        cipher.init(2, ((java.security.Key) (obj)), ((java.security.spec.AlgorithmParameterSpec) (obj1))); //初期化(キーの設定)
        bundle = cipher.doFinal(bundle); //暗号化実行
        obj = new ImageView(this);
        ((ImageView) (obj)).setImageBitmap(BitmapFactory.decodeByteArray(bundle, 0, bundle.length)); //ビットマップにデコードし、画像表示
        setContentView(((android.view.View) (obj)));
        return;
    }
}

コードの処理は大まかに前半パートと後半パートに分かれており、前半パートではスマホのIMEI(端末識別番号)のチェックを行い、後半パートではそのIMEIを使って暗号処理を行い、画像を作成している。最終的に作成される画像にフラグが含まれていると思われる。

まずは前半パートから見ていく。
前半パートのコードを見ていくと、IMEIが特定の値である場合だけ後半パートの処理に進めるようになっている。
IMEIのヒントとして、先頭8文字が99999991であること、IMEIをsha256でハッシュ化した際の値が356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5であることが分かっている。また、IMEIは15桁の数字であることが決められている。
これらのヒントから正しいIMEIを導き出すことが可能になっている。
例として以下のようなプログラムを作成した。

import hashlib

head = "99999991"
for i in range(9999999):
    imei = head + str(i)
    hash = hashlib.sha256(imei.encode()).hexdigest()
    if hash == "356280a58d3c437a45268a0b226d8cccad7b5dd28f5d1b37abf1873cc426a8a5":
        print("IMEI:" + imei)
        break

上記プログラムを実行すると、IMEIが999999913371337であることが判明した。

次に後半パートを見ていく。
後半パートではAES-CBC暗号(PKCS#5パディング)による暗号化処理を行っている。
暗号化するデータは最初に見つけた怪しい画像(jewel_c.png)になる。直感的にこのファイルを暗号化するんだろうなということが分かると思うが、ちゃんとリソースID(0x7f040000)を確認したい場合はAPKファイルをAndroid Studio等で開くと確認できる。

そして秘密鍵はIMEIの先頭に!を付けた!999999913371337で、IV(初期化ベクトル)はkLwC29iMc4nRMuE5になる。
この設定で暗号化処理を行い、その結果をファイルとして出力すると画像ファイルを得られる。

後半パートの処理もPythonでやろうと思ったが、PKCS#5パディングを使った簡単な暗号化方法が分からなかった…。そのため、結局以下のようなJavaプログラムを書いた。(デコンパイルされたコードとほとんど同じ内容)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class hack {
    public static void main(String[] args) throws Exception {
        
        // 画像ファイル読み込み
        FileInputStream fis = new FileInputStream("jewel_c.png");
        byte bundle[] = new byte[fis.available()];
        fis.read(bundle);
        fis.close();

        // 秘密鍵
        Object obj = "999999913371337";
        obj = new SecretKeySpec((new StringBuilder("!")).append(((String) (obj))).toString().getBytes("ASCII"), "AES");
        
        // IV
        Object obj1 = new IvParameterSpec("kLwC29iMc4nRMuE5".getBytes());
        
        // 暗号化の実行
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, ((java.security.Key) (obj)), ((java.security.spec.AlgorithmParameterSpec) (obj1)));
        bundle = cipher.doFinal(bundle);

        // 暗号データを画像ファイルとして出力
        FileOutputStream fos = new FileOutputStream("flag.png");
        fos.write(bundle);
        fos.close();
    } 
}

その結果、以下のような画像を得られた。

ということで、この画像のコメントにフラグが記載されていた。

所感

CTFでリバエン問題を解いたのはたぶん初めてで、APKファイルの解析も初めての経験だった。
特にAPKファイルの解析は何かしらで役立てそう。APKのマルウェアを解析してみたいなぁー

#18 USB flash drive (write-up)

問題

ksnctf.sweetduet.info

zipファイルが用意されている。
その中にはdrive.imgというイメージファイルがある。

解法

とりあえずfileコマンドで確認。

中身もイメージファイルっぽい。
イメージファイルなのでフォレンジック問題か?と思いつつ、とりあえずVolatilityでOSプロファイルを確認してみる。

OSの種類は分からず。問題名の通り、USBにバックアップされたイメージファイルか?
次にAutopsyで分析してみる。すると画像が複数枚見つかり、その内の1枚が以下のような画像だった。

ちなみにこれは削除された画像。ヒントも書かれている。
次に削除されたほかのファイルを確認してみると…

フラグが分割されて記述されていた。
これを繋げるとフラグを獲得。

別の方法

コマンド操作で完結したい場合、まずはThe Sleuth Kitのflsコマンドを実行。

inodeが36-128-*の削除されたファイルが怪しい。
icatコマンドで36-128-1を復元すると先ほどの画像を確認できる。
36-128-436-128-10のファイルを復元し、中身を確認するとフラグを獲得できる。

所感

フォレンジックツールを知ってれば一瞬で解ける問題。 。