0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SECCON Beginners CTF 2020 "siblangs" writeup

Posted at

問題

Well, they look so similar... siblangs.apk

(SHA-1 hash: c08d002c5837ad39d509a1d09ed623003ae97229)

解法

Step 1. apktool

apk ファイルのリバースエンジニアリング問題

まずは apktool を使って中のファイルを取り出す

apktool d -s -o decompiled siblangs.apk

classes.dex が出てくるほか、rg ctf4bを実行すると assets/index.android.bundle がヒットする

Step 2. assets/index.android.bundle の解析

ファイルを眺めていると React の文字が出てくるので React Native で作ったアプリだと思われる

ripgrep で ctf4b を検索すると出てきたところを抜粋

function v() {
  var t;
  (0, l.default)(this, v);
  for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++)
    n[c] = arguments[c];
  return (
    ((t = y.call.apply(y, [this].concat(n))).state = {
      flagVal: "ctf4b{",
      xored: [
        34,
        63,
        3,
        77,
        36,
        20,
        24,
        8,
        25,
        71,
        110,
        81,
        64,
        87,
        30,
        33,
        81,
        15,
        39,
        90,
        17,
        27,
      ],
    }),
    (t.handleFlagChange = function (o) {
      t.setState({ flagVal: o });
    }),
    (t.onPressValidateFirstHalf = function () {
      if ("ios" === h.Platform.OS) {
        for (
          var o = "AKeyFor" + h.Platform.OS + "10.3",
            l = t.state.flagVal,
            n = 0;
          n < t.state.xored.length;
          n++
        )
          if (
            t.state.xored[n] !==
            parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)
          )
            return void h.Alert.alert("Validation A Failed", "Try again...");
        h.Alert.alert(
          "Validation A Succeeded",
          "Great! Have you checked the other one?"
        );
      } else
        h.Alert.alert(
          "Sorry!",
          "Run this app on iOS to validate! Or you can try the other one :)"
        );
    }),
    (t.onPressValidateLastHalf = function () {
      "android" === h.Platform.OS
        ? p.default.validate(t.state.flagVal, function (t) {
            t
              ? h.Alert.alert(
                  "Validation B Succeeded",
                  "Great! Have you checked the other one?"
                )
              : h.Alert.alert(
                  "Validation B Failed",
                  "Learn once, write anywhere ... anywhere?"
                );
          })
        : h.Alert.alert(
            "Sorry!",
            "Run this app on Android to validate! Or you can try the other one :)"
          );
    }),
    t
  );
}

onPressValidateFirstHalf で入力を XOR でチェックしているのでもとに戻す

a = [34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]

key = "AKeyForios10.3"

ans = ""

for i in range(len(a)):
    ans += chr(a[i] ^ ord(key[i % len(key)]))

print(ans)

# ctf4b{jav4_and_j4va5cr

onPressValidateLastHalf で p.default.validate を呼んでいるがこのファイルの中にはないので java で処理していると見当をつけて先程出てきた classes.dex を解析する

Step 3. classes.dex の解析

dex2jar で dex ファイルを jar ファイルに戻す

https://sourceforge.net/projects/dex2jar/

chmod +x d2j-dex2jar.sh
./d2j-dex2jar.sh ../classes.dex

classes-dex2jar.jar が生成される

JD-GUI で javaのソースコードを調べる

http://java-decompiler.github.io/

JD-GUI でclasses-dex2jar.jarを開くとes.o0i.challengeapp/nativemodule/ValidateFlagModuleにてValidateするコードがある

package es.o0i.challengeapp.nativemodule;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ValidateFlagModule extends ReactContextBaseJavaModule {
  private static final int GCM_IV_LENGTH = 12;
  private final ReactApplicationContext reactContext;
  private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
  private final SecureRandom secureRandom = new SecureRandom();
  public ValidateFlagModule(ReactApplicationContext paramReactApplicationContext) {
    super(paramReactApplicationContext);
    this.reactContext = paramReactApplicationContext;
  }
  public String getName() {
    return "ValidateFlagModule";
  }
  @ReactMethod
  public void validate(String paramString, Callback paramCallback) {
    byte[] arrayOfByte = new byte[43];
    arrayOfByte[0] = 95;
    arrayOfByte[1] = -59;
    arrayOfByte[2] = -20;
    arrayOfByte[3] = -93;
    arrayOfByte[4] = -70;
    arrayOfByte[5] = 0;
    arrayOfByte[6] = -32;
    arrayOfByte[7] = -93;
    arrayOfByte[8] = -23;
    arrayOfByte[9] = 63;
    arrayOfByte[10] = -9;
    arrayOfByte[11] = 60;
    arrayOfByte[12] = 86;
    arrayOfByte[13] = 123;
    arrayOfByte[14] = -61;
    arrayOfByte[15] = -8;
    arrayOfByte[16] = 17;
    arrayOfByte[17] = -113;
    arrayOfByte[18] = -106;
    arrayOfByte[19] = 28;
    arrayOfByte[20] = 99;
    arrayOfByte[21] = -72;
    arrayOfByte[22] = -3;
    arrayOfByte[23] = 1;
    arrayOfByte[24] = -41;
    arrayOfByte[25] = -123;
    arrayOfByte[26] = 17;
    arrayOfByte[27] = 93;
    arrayOfByte[28] = -36;
    arrayOfByte[29] = 45;
    arrayOfByte[30] = 18;
    arrayOfByte[31] = 71;
    arrayOfByte[32] = 61;
    arrayOfByte[33] = 70;
    arrayOfByte[34] = -117;
    arrayOfByte[35] = -55;
    arrayOfByte[36] = 107;
    arrayOfByte[37] = -75;
    arrayOfByte[38] = -89;
    arrayOfByte[39] = 3;
    arrayOfByte[40] = 94;
    arrayOfByte[41] = -71;
    arrayOfByte[42] = 30;
    try {
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
      GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
      cipher.init(2, this.secretKey, gCMParameterSpec);
      arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
      byte[] arrayOfByte1 = paramString.getBytes();
      for (int i = 0;; i++) {
        if (i < arrayOfByte.length) {
          if (arrayOfByte1[i + 22] != arrayOfByte[i]) {
            paramCallback.invoke(new Object[] { Boolean.valueOf(false) });
            return;
          }
        } else {
          paramCallback.invoke(new Object[] { Boolean.valueOf(true) });
          return;
        }
      }
    } catch (Exception exception) {
      paramCallback.invoke(new Object[] { Boolean.valueOf(false) });
      return;
    }
  }
}

arrayOfByte を出力して正しいフラグを調べる

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Generate{
  public static void main(String[] args){
    SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
    byte[] arrayOfByte = new byte[43];
    arrayOfByte[0] = 95;
    arrayOfByte[1] = -59;
    arrayOfByte[2] = -20;
    arrayOfByte[3] = -93;
    arrayOfByte[4] = -70;
    arrayOfByte[5] = 0;
    arrayOfByte[6] = -32;
    arrayOfByte[7] = -93;
    arrayOfByte[8] = -23;
    arrayOfByte[9] = 63;
    arrayOfByte[10] = -9;
    arrayOfByte[11] = 60;
    arrayOfByte[12] = 86;
    arrayOfByte[13] = 123;
    arrayOfByte[14] = -61;
    arrayOfByte[15] = -8;
    arrayOfByte[16] = 17;
    arrayOfByte[17] = -113;
    arrayOfByte[18] = -106;
    arrayOfByte[19] = 28;
    arrayOfByte[20] = 99;
    arrayOfByte[21] = -72;
    arrayOfByte[22] = -3;
    arrayOfByte[23] = 1;
    arrayOfByte[24] = -41;
    arrayOfByte[25] = -123;
    arrayOfByte[26] = 17;
    arrayOfByte[27] = 93;
    arrayOfByte[28] = -36;
    arrayOfByte[29] = 45;
    arrayOfByte[30] = 18;
    arrayOfByte[31] = 71;
    arrayOfByte[32] = 61;
    arrayOfByte[33] = 70;
    arrayOfByte[34] = -117;
    arrayOfByte[35] = -55;
    arrayOfByte[36] = 107;
    arrayOfByte[37] = -75;
    arrayOfByte[38] = -89;
    arrayOfByte[39] = 3;
    arrayOfByte[40] = 94;
    arrayOfByte[41] = -71;
    arrayOfByte[42] = 30;
    System.out.println(arrayOfByte);
    try {
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
      GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
      cipher.init(2, secretKey, gCMParameterSpec);
      arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
      for (int i = 0;; i++) {
        if (i < arrayOfByte.length) {
          System.out.println(arrayOfByte[i]);
        }
      }
    } catch (Exception exception) {
      System.out.println("Fail");
      return;
    }
  }
}
b = [49, 112, 116, 95, 51, 118, 101, 114, 121, 119, 104, 101, 114, 101, 125]

ans = ""

for i in range(len(b)):
    ans += chr(b[i])
    print(ans)

print(ans)

# 1pt_3verywhere}

よってフラグは

ctf4b{jav4_and_j4va5cr1pt_3verywhere}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?