アイジア

CTF, 情報セキュリティの学んだことメモ

ksnctf 21 Perfect Cipher

ksnctfの21問目を解いていきます。メルセンヌツイスタの問題です。 ksnctf.sweetduet.info

大まかな解法


1. encrypt.keyを復元する
2.encrypt.keyからメルセンヌツイスタの乱数を予測し、flag.keyを生成する
3.flag.keyとflag.encからflag_dec.jpgを復元する

1. encrypt.keyを復元する

encrypt.zipには以下のファイルがあります。
f:id:favoritte15:20181129091148p:plain
↓encrypt.cpp

//  g++ -O2 -o encrypt.exe encrypt.cpp mt19937ar.cpp

#include <stdio.h>
#include <stdlib.h>
#include "mt19937ar.h"

typedef unsigned long dword;

void initialize(const char *seed);
void encrypt(const char *plain, const char *crypt, const char *key);
void decrypt(const char *plain, const char *crypt, const char *key);
int min(int a, int b) { return a<b ? a : b; }

int main()
{
    initialize("seed");
    
    encrypt("encrypt.cpp", "encrypt.enc", "encrypt.key");
    encrypt("flag.jpg", "flag.enc", "flag.key");
    
    //  decrypt("encrypt_dec.cpp", "encrypt.enc", "encrypt.key");
    //  decrypt("flag_dec.jpg", "flag.enc", "flag.key");
}

void initialize(const char *seed)
{
    const int N = 1024;
    
    FILE *f = fopen(seed, "rb");
    if (f==NULL)
    {
        printf("Failed to open %s\n", seed);
        exit(-1);
    }
    
    dword buf[N];
    fread(buf, sizeof (dword), N, f);
    
    fclose(f);
    
    init_by_array(buf, N);
}

void encrypt(const char *plain, const char *crypt, const char *key)
{
    FILE *fp = fopen(plain, "rb");
    if (fp==NULL)
    {
        printf("Failed to open %s\n", plain);
        exit(-1);
    }
    FILE *fc = fopen(crypt, "wb");
    if (fc==NULL)
    {
        printf("Failed to open %s\n", crypt);
        exit(-1);
    }
    FILE *fk = fopen(key, "wb");
    if (fk==NULL)
    {
        printf("Failed to open %s\n", key);
        exit(-1);
    }
    
    fseek(fp, 0, SEEK_END);
    dword length = (dword)ftell(fp);
    fseek(fp, 0, SEEK_SET);
    
    fwrite(&length, 4, 1, fc);
    
    for (int i=0; i<length; i+=4)
    {
        dword p;
        fread(&p, 4, 1, fp);
        dword k = genrand_int32();
        dword c = p^k;
        
        fwrite(&c, 4, 1, fc);
        fwrite(&k, 4, 1, fk);
    }
    
    fclose(fp);
    fclose(fc);
    fclose(fk);
}

void decrypt(const char *plain, const char *crypt, const char *key)
{
    FILE *fp = fopen(plain, "wb");
    if (fp==NULL)
    {
        printf("Failed to open %s\n", plain);
        exit(-1);
    }
    FILE *fc = fopen(crypt, "rb");
    if (fc==NULL)
    {
        printf("Failed to open %s\n", crypt);
        exit(-1);
    }
    FILE *fk = fopen(key, "rb");
    if (fk==NULL)
    {
        printf("Failed to open %s\n", key);
        exit(-1);
    }
    
    dword length;
    fread(&length, 4, 1, fc);
    
    for (int i=0; i<length; i+=4)
    {
        dword c;
        fread(&c, 4, 1, fc);
        dword k;
        fread(&k, 4, 1, fk);
        dword p = c^k;
        
        fwrite(&p, min(4,length-i), 1, fp);
    }
    
    fclose(fp);
    fclose(fc);
    fclose(fk);
}


encrypt関数において、
①引数で指定したファイルを開く
②第一引数で指定したファイル(fp)のサイズを取得して.encファイルの先頭4バイトに書き込む ③genrand_int32関数でメルセンヌツイスタの疑似乱数を生成
④fpと生成した疑似乱数を4バイト単位でXORしたものを.encファイルに出力(生成した乱数は.keyファイルに記録する)
というような操作が行われています。

暗号化と言っても単にXORしているだけなのでXORの性質を利用すればencrypt.keyファイルの復元が可能です。
いま、encrypt関数で
encrypt .cpp (XOR) encrypt.key = encrypt.enc
が計算されているので、
encrypt.cpp (XOR) encrypt.enc(先頭4バイト除く) = encrypt.key
となり、生成された乱数を得ることができます。

2.encrypt.keyからメルセンヌツイスタの乱数を予測し、flag.keyを生成する

メルセンヌ・ツイスタをわかった気になる | 成瀬順のメモ帳
(メルセンヌツイスタのしくみについて書かれています)

メルセンヌツイスタは連続した624個の乱数があれば次に生成される乱数の予測が可能になります。先ほど復元したencrypt.keyは2600バイト(=650個の乱数)なので次に生成されたflag.keyを予測できます。これを一から実装するのは大変なので、

Mersenne Twisterの出力を推測してみる - ももいろテクノロジー
こちらのpredict_mt.pyをお借りして、改造します。

import random
from struct import *

def untemper(x):
    x = unBitshiftRightXor(x, 18)
    x = unBitshiftLeftXor(x, 15, 0xefc60000)
    x = unBitshiftLeftXor(x, 7, 0x9d2c5680)
    x = unBitshiftRightXor(x, 11)
    return x

def unBitshiftRightXor(x, shift):
    i = 1
    y = x
    while i * shift < 32:
        z = y >> shift
        y = x ^ z
        i += 1
    return y

def unBitshiftLeftXor(x, shift, mask):
    i = 1
    y = x
    while i * shift < 32:
        z = y << shift
        y = x ^ (z & mask)
        i += 1
    return y


#encrypt.keyから乱数を624読み込みメルセンヌツイスタの内部状態を復元
with open("encrypt.key", "rb") as f:
    values1 = [int.from_bytes(f.read(4), 'little') for i in range(624)]
    mt_state = tuple([untemper(x) for x in values1] + [624])
    random.setstate((3, mt_state, None))

#650個まで分かってるのでいらない
waste=[random.getrandbits(32) for i in range(26)]

#バイト数が78556 → 19639個の乱数が復元に必要
pre = [random.getrandbits(32) for i in range(19639)]


#生成された乱数を4バイト単位でflag.keyに書き込み
with open("flag.key", "wb") as f:
    for x in pre:
        f.write(pack('<L', x))

3.flag.keyとflag.encからflag_dec.jpgを復元する

flag.keyが分かったのであとはdecrypt関数にかければflag_dec.jpgが復元されます。flag_dec.jpgをバイナリエディタで開くとちょっと下にフラグが出てきます。

ksnctf 20 G00913

ksnctf.sweetduet.info

first 10-digit prime found in consecutive digits of π

円周率の中で、最初の10桁の素数を見つけよ。

素数判定いろいろ - シンプルな判定と、素数の分布 - Qiita
上のリンクのsimple_prime_test.pyで素数判定処理がありますのでそれを拝借させていただきます。

import math

def is_prime(n):
    if n == 1: return False

    for k in range(2, int(math.sqrt(n)) + 1):
        if n % k == 0:
            return False

    return True

#円周率 とりあえず100桁
pi = "31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
x = 0

while(x < 90):
    num = int(pi[x:x+10])   #10桁の円周率を抽出
    if(is_prime(num)):
        print(num)
        break
    x += 1

ksnctf 19 ZIP de kure

ksnctf.sweetduet.info

flag.zipにはパスワードがかかっています。
f:id:favoritte15:20181124101600p:plain

It is known that the encryption system of ZIP is weak against known-plaintext attacks

zipは既知平文攻撃に弱いことが知られています。
既知平文攻撃とは、平文の一部と暗号文から鍵を推測し、平文全体を割り出す攻撃手法です。ここでは平文が「flag.html」「Standard-lock-key.jpg」で鍵がパスワード、暗号文がzipファイルです。ここで平文の一部を知りたいのですが「Standard-lock-key.jpg」はネットで出回っているようでした。
...出回っているのですがサイズがいろいろあり、正しい画像を使わないと復号できないみたいです。250kBぐらいのこれを使ってください。

既知平文攻撃はpkcrackを使って行います。

パスワードクラッキングツール pkcrack
引数は以下の通り。

 -C [暗号化されたzipファイル]
 -c [暗号化されたzipファイルの中で平文がわかるファイル]
 -P [平文のファイルが入っている暗号化されていないzip]
 -p [平文のファイル]
 -d [出力先(復号したzipファイルの名前)]

./pkcrack -C flag.zip -c Standard-lock-key.jpg -p flag.zip -p Stadard-lock-key.jpg -d flag_dec.zip

うまくいくとflag_dec.zipが作成され、unzipしてflag.htmlをブラウザで開くとフラグが表示されます。

ksnctf 18 USB flash drive

ksnctfの18問目を解いていきます。ジャンルはディジタルフォレンジックスです。

ksnctf.sweetduet.info

zipファイルを解凍してimgファイルに探りを入れます。

unzip drive.zip
fls drive.img

f:id:favoritte15:20181124095333p:plain
イメージファイルは.jpgの集まりみたいですが「Liberty Leading the People.jpg」が消去されています。

icat drive.img 36-128-4 > 0.jpg

試しにLiberty Leading the People.jpg:00を復元するとこれは画像ファイルでなく「FLA」と書かれた3バイトのファイルでした。00~06のファイルを復元してcatコマンドでつなげて出力するとフラグが出てきました。

おまけ:FTK Imagerで開く

f:id:favoritte15:20181124100402p:plain
FTK Imagerでファイルを開くと自動で画像ファイルを復元してくれるのでツールを使うだけの問題になります。

ksnctf 16 Math I

ksnctfの16問目を解いていきます。

ksnctf.sweetduet.info

一見すると単なる数学の問題のようですが、これはRSAに使われる暗号化の技術と関連しています。

RSAとは

RSA暗号 - Wikipedia
大きな素数を掛け算する(=暗号化する)ことは簡単でも、その積を素因数分解する(=復号する)のが難しいということを利用した暗号です。今の問題でいうと、pとqが大きな素数、nがその積、cが暗号文、eが秘密鍵(の一部)に当たります。

RSAによる暗号化、復号の流れ

RSAの処理の流れについて超ざっくり説明します。間違ってたらすいません。
①巨大な素数p,qを用意しn=pqを計算しておく
※ここのpqは秘密にしておかなければなりません。この問題では明らかになっているので復号が可能になります。

②p-1とq-1の最小公倍数をもとめる
※最小公倍数L = lcm(p-1, q-1)

③ed≡1(mod L) (ed-1がLの倍数である)となるようなedの組を求める
※ここで、(e,n)が公開鍵、(d, n)が秘密鍵になります。

④送信者は平文mを公開鍵(e,n)を使って
c= me mod n (mのe乗をnで割った余り)を計算して暗号化する
受信者は秘密鍵を使って
m = cd mod n を計算し復号する



mを求める

要するにmを求めるためにはm = cd mod n を計算すればいいわけです。いまc、とnは分かっているので①~③の流れに沿ってdを求めます。
①巨大な素数p,qを用意しn=pqを計算しておく
いまpもqもnもわかっているのでOKです。

②p-1とq-1の最大公倍数をもとめる
a,bの最小公倍数lcm(a,b)と最大公約数gcd(a,b)の間には次の式が成り立ちます。

lcm(a,b) = ab/gcd(a,b)

ですので、まずgcd(p-1,q-1)を求める必要があります。最大公約数を求めるアルゴリズムは、ユークリッドの互除法が有名です。でもpython3.5からmathモジュールでgcd関数が利用可能になったので詳しい説明は飛ばします。

import math

p = 34111525225922333955113751419357677129436029651245533697825114748126342624744832960936498161825269430327019858323450578875242014583535842110912370431931233957939950911741013017595977471949767235426490850284286661592357779825212265055931705799916913817655743434497422993498931394618832741336247426815710164342599150990608143637331068220244525541794855651643135012846039439355101027994945120698530177329829213208761057392236875366458197098507252851244132455996468628957560178868724310000317011912994632328371761486669358065577269198065792981537378448324923622959249447066754504943097391628716371245206444816309511381323
q = 44481453884385518268018625442920628989497457642625668259648790876723318635861137128631112417617317160816537010595885992856520476731882382742220627466006460645416066646852266992087386855491152795237153901319521506429873434336969666536995399866125781057768075533560120399184566956433129854995464893265403724034960689938351450709950699740508459206785093693277541785285699733873530541918483842122691276322286810422297015782658645129421043160749040846216892671031156465364652681036828461619272427318758098538927727392459501761203842363017121432657534770898181975532066012149902177196510416802134121754859407938165610800223


lcm = (p-1)*(q-1)//math.gcd(p-1, q-1)

print(lcm)

Lが「84296124236828755295256160493851228647282615601483997160948130049067122531755132909036158809504235996655857568141379623584785421268598355704500296885003303538029386515098788842365280739235449990183062094279553798371687347273163100041038785672404220512403565798591569237642461494493666961096181434014181929065329176973463601725920953714877046441949558852569160310401566464294319909521197662482014455011640926123889364512056036070109707528066878526609938532349593490438006567548470577849626240317548045332059115332702394504908613775263079301368965937265158080043479607415842506120690972879040939308376545429013819648390485935794450615898958353067616834620525918090857785857386064201656062162479977365475744402936160462725848773696706919076088977774264353849497982111223048654302053359923581516772918609667237350631341932314473210047462494239592712774736257925119172802057115385038544143487469511024761330324631420828143510952462371722743318909328271983585955272776528260618472853022289420098014703890951951042220157282038147741762284049660911709073315391605377178884477278893384365501359415452208704330546728410396192866583877887324203642431037251063767954409221007463068318828757916045833958167479029226438074023231872818141833569638」だとわかりました。

③ed≡1(mod L) (ed-1がLの倍数である)となるようなedの組を求める
このedの組を求めるには拡張ユークリッド互除法を使います。

Python で RSA 公開鍵暗号をなぞってみる - CAMPHOR- Tech Blog

拡張ユークリッド互除法はax + by = gcd(a,b)(x,yは正の整数)となるa,b,gcd(x,y)を同時に求めるアルゴリズムです。x=e、y=L、gcd(x,y) = gcd(e, L) = 1を代入して求めたaが秘密鍵dに対応します。

def ex_euclid(x, y):
    c0, c1 = x, y
    a0, a1 = 1, 0
    b0, b1 = 0, 1

    while c1 != 0:
        m = c0 % c1
        q = c0 // c1

        c0, c1 = c1, m
        a0, a1 = a1, (a0 - q * a1)
        b0, b1 = b1, (b0 - q * b1)

    return a0

e = 65537
L = 84296124236828755295256160493851228647282615601483997160948130049067122531755132909036158809504235996655857568141379623584785421268598355704500296885003303538029386515098788842365280739235449990183062094279553798371687347273163100041038785672404220512403565798591569237642461494493666961096181434014181929065329176973463601725920953714877046441949558852569160310401566464294319909521197662482014455011640926123889364512056036070109707528066878526609938532349593490438006567548470577849626240317548045332059115332702394504908613775263079301368965937265158080043479607415842506120690972879040939308376545429013819648390485935794450615898958353067616834620525918090857785857386064201656062162479977365475744402936160462725848773696706919076088977774264353849497982111223048654302053359923581516772918609667237350631341932314473210047462494239592712774736257925119172802057115385038544143487469511024761330324631420828143510952462371722743318909328271983585955272776528260618472853022289420098014703890951951042220157282038147741762284049660911709073315391605377178884477278893384365501359415452208704330546728410396192866583877887324203642431037251063767954409221007463068318828757916045833958167479029226438074023231872818141833569638

print(ex_euclid(e, L))

dは「10136834995658138463492588932237385491695291111208864940803396751708164741638497374858690015986433371830337267414166239124032133070140892035143000743865465846517380947029823685348440995253285645858564053359433045225861238443319016607770063778998392692040412317602272871230911378886805763467949492370199548086788520129512590524466836080793231350367036533822078404661103579735165405907144952897153606664124725556286862104147483410417544364690099785895187841577233414683948452917428271419700389092308102983993131939774899233916486643618846269650561065529192835021784072906056956997378054492267293936086722836352868038649395298228421583287298637113781349064564517150221252274929575231903374062018473558712091515320198980831322980690354261397968433615194125039106056075477193744671780559524508993907065803481811763741483524857261750894670978486995592861707073084026797089476358795024013403036830297639289928502806357134849001477277811794054352447082046958247110998439840383629616621979472098506072201677900915912594977791777814705476731626338978671272819912434837992993096501746475459427746365460409490804111246572200930710614577133985413566473885050820659402302501957059621914654156295472133564617207107883082204882388427753479344345367」でした。

受信者は秘密鍵を使って
m = cd mod n を計算し復号する

c = 225549592628492616152632265482125315868911125659971085929712296366214355608049224179339757637982541542745010822022226409126123627804953064072055667012172681551500780763483172914389813057444669314726404135978565446282309019729994976815925850916487257699707478206132474710963752590399332920672607440793116387051071191919835316845827838287954541558777355864714782464299278036910958484272003656702623646042688124964364376687297742060363382322519436200343894901785951095760714894439233966409337996138592489997024933882003852590408577812535049335652212448474376457015077047529818315877549614859586475504070051201054704954654093482056493092930700787890579346065916834434739980791402216175555075896066616519150164831990626727591876115821219941268309678240872298029611746575376322733311657394502859852213595389607239431585120943268774679785316133478171225719729917877009624611286702010936951705160870997184123775488592130586606070277173392647225589257616518666852404878425355285270687131724258281902727717116041282358028398978152480549468694659695121115046850718180640407034795656480263573773381753855724693739080045739160297875306923958599742379878734638341856117533253251168244471273520476474579680250862738227337561115160603373096699944163

d = 10136834995658138463492588932237385491695291111208864940803396751708164741638497374858690015986433371830337267414166239124032133070140892035143000743865465846517380947029823685348440995253285645858564053359433045225861238443319016607770063778998392692040412317602272871230911378886805763467949492370199548086788520129512590524466836080793231350367036533822078404661103579735165405907144952897153606664124725556286862104147483410417544364690099785895187841577233414683948452917428271419700389092308102983993131939774899233916486643618846269650561065529192835021784072906056956997378054492267293936086722836352868038649395298228421583287298637113781349064564517150221252274929575231903374062018473558712091515320198980831322980690354261397968433615194125039106056075477193744671780559524508993907065803481811763741483524857261750894670978486995592861707073084026797089476358795024013403036830297639289928502806357134849001477277811794054352447082046958247110998439840383629616621979472098506072201677900915912594977791777814705476731626338978671272819912434837992993096501746475459427746365460409490804111246572200930710614577133985413566473885050820659402302501957059621914654156295472133564617207107883082204882388427753479344345367

n = 1517330236262917595314610888889322115651087080826711948897066340883208205571592392362650858571076247939805436226544833224526137582834770402681005343930059463684528957271778199162575053306238099823295117697031968370690372250916935800738698142103275969223264184374648246277564306900886005299731265812255274723175925185522344831066577166867786835955092059346244885587228196357297758371381557924676260190209536670230008561217008649261974735505203813478978893582292682827884118215872470401293272325715864815977064075988643101088355047954735427424641386870772845440782632933485165110172437511822736907550777817722248753671107339823410418938404382732079381329288400012929311347390423061254658780185245562668131009832293474920208834795460061115101364091252176594144096675899952570380792978037217747311595899301451192342027799533264325948876556110474850761538179748318187805312451895898751337975457949549497666542175077894987697085521882531938339334715190663665300179658557458036053188152532948734992896239950564081581184284728802682982779186068791931259198917308153082917381616147108543673346682338045309449569430550618884202465809290850964525390539782080230737593560891353558335337408957948041667929154230334506735825418239563481028126435029


m = pow(c,d,n)
print ("%0512x"%m).decode("hex")

実行するとフラグが出力されます。

ksnctf 15 Jewel

ksnctfの15問目を解いていきます。Javaと暗号の問題です。

ksnctf.sweetduet.info

アプリのインストールと実行

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

↑で出力したファイルを開くと、
f:id:favoritte15:20181121162140p:plain
ちゃんと復元されていました。