Đầu tiên đề cho ta 1 file apk chỉ dùng để check flag cơ bản
Mình đã dùng trang Android apk decompiler để decompile file để đọc source
Down source về ta tìm trong đường dẫn \sources\com\example\babydroid
sẽ có các file java của chương trình gốc.
Đọc thử file FlagValidator.java
(file dùng để check flag) ta thấy có 1 hàm check flag với 1 loạt các điều kiên:
package com.example.babydroid;
import android.content.Context;
public class FlagValidator {
public static boolean checkFlag(Context ctx, String flag) {
String regex = retriever();
if (flag.startsWith("HCMUS-CTF{") &&
flag.charAt(19) == '_' &&
flag.length() == 37 &&
flag.toLowerCase().substring(10).startsWith("this_is_") &&
flag.charAt(((int) (((double) MagicNum.obtainY()) * Math.pow((double) MagicNum.obtainX(), (double) MagicNum.obtainY()))) + 2) == flag.charAt(((int) Math.pow(Math.pow(2.0d, 2.0d), 2.0d)) + 3) &&
new StringBuilder(flag).reverse().toString().toLowerCase().substring(1).startsWith(ctx.getString(C0095R.string.last_part)) &&
new StringBuilder(flag).reverse().toString().charAt(0) == '}' &&
Helper.ran(flag.toUpperCase().substring((MagicNum.obtainY() * MagicNum.obtainX() * MagicNum.obtainY()) + 2, (int) (Math.pow((double) MagicNum.obtainZ(), (double) MagicNum.obtainX()) + 1.0d))).equals("ERNYYL") &&
flag.toLowerCase().charAt(18) == 'a' &&
flag.charAt(18) == flag.charAt(28) &&
flag.toUpperCase().charAt(27) == flag.toUpperCase().charAt(28) + 1) {
return flag.substring(10, flag.length() - 1).matches(regex);
}
return false;
}
}
Kiểm tra hết các mệnh đề cơ bản, ta được 1 phần của flag và biết được len(flag) = 37 kí tự:
HCMUS-CTF{this_is_a_*******ba*******}
Và hiện giờ ta chỉ cần check thêm 3 điều kiện:
flag.charAt(((int) (((double) MagicNum.obtainY()) * Math.pow((double) MagicNum.obtainX(), (double) MagicNum.obtainY()))) + 2) == flag.charAt(((int) Math.pow(Math.pow(2.0d, 2.0d), 2.0d)) + 3) &&
new StringBuilder(flag).reverse().toString().toLowerCase().substring(1).startsWith(ctx.getString(C0095R.string.last_part)) &&
Helper.ran(flag.toUpperCase().substring((MagicNum.obtainY() * MagicNum.obtainX() * MagicNum.obtainY()) + 2, (int) (Math.pow((double) MagicNum.obtainZ(), (double) MagicNum.obtainX()) + 1.0d))).equals("ERNYYL")
để biết được MagicNum.obtain
mình đã kiểm tra trong file MagicNum.java
:
public static int obtainX() {
return 2;
}
public static int obtainY() {
return 3;
}
public static int obtainZ() {
return 5;
}
Thay các số tương ứng, ta được các điều kiện ngắn hơn:
check điều kiện đầu tiên:
flag.charAt(((int) (((double) 3) * Math.pow((double) 2, (double) 3))) + 2) == flag.charAt(((int) Math.pow(Math.pow(2.0d, 2.0d), 2.0d)) + 3)
đoạn này chạy các code bên trong sẽ ra 1 con số cụ thể, sau đó, đoạn này sẽ có nghĩa là flag[26]==flag[19] (kí tự '_')
=> Flag hiện tại HCMUS-CTF{this_is_a_******_ba*******}
Helper.ran(flag.toUpperCase().substring((3 * 2 * 3) + 2, (int) (Math.pow((double) 5, (double) 2) + 1.0d))).equals("ERNYYL")
Đoạn này sẽ thay thế vào chổ thiếu 6 kí tự, kiểm tra hàm ran()
trong file Helper.java
:
public static String ran(String s) {
String out = "";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 'a' && c <= 'm') {
c = (char) (c + 13);
} else if (c >= 'A' && c <= 'M') {
c = (char) (c + 13);
} else if (c >= 'n' && c <= 'z') {
c = (char) (c - 13);
} else if (c >= 'N' && c <= 'Z') {
c = (char) (c - 13);
}
out = out + c;
}
return out;
}
Hàm này chỉ thay đổi từng kí tự cơ bản, dùng nó với chuỗi đề cho "ERNYYK"
, ta được chuỗi "REALLY"
Flag HCMUS-CTF{this_is_a_REALLY_ba*******}
chỉ cần tìm phần cuối thông qua:
new StringBuilder(flag).reverse().toString().toLowerCase().substring(1).startsWith(ctx.getString(C0095R.string.last_part))
tạm thời ta chỉ quan tâm tới ctx.getString(C0095R.string.last_part)
, kiểm tra trong file C0095R.java
: ta có được id của last_part:
public static final int last_part = 2131623979;
Qua tìm hiểu, thì context.getString(<id>)
sẽ trả về chuỗi của trương trình với id
tương ứng;
ta tìm các string của chương trình nằm ở \resources\res\values\strings.xml
:
Đảo ngược chuỗi và ghép vào flag, ta được:
HCMUS-CTF{this_is_a_REALLY_basic_rev}
Sau khi pass toàn bộ điều kiện của if
, hàm checkFlag
còn có đoạn kiểm tra flag người dùng với regex
mà chương trình có thông qua hàm retriever()
nằm trong file Helper.java
:
public static String retriever() {
StringBuilder sb;
String str;
String r = "";
boolean upper = true;
for (int i = 0; i < 26; i++) {
if (upper) {
sb = new StringBuilder();
sb.append(r);
str = "[A-Z_]";
} else {
sb = new StringBuilder();
sb.append(r);
str = "[a-z_]";
}
sb.append(str);
r = sb.toString();
upper = !upper;
}
return r;
}
Chạy đoạn này, ta được regex: [A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_][A-Z_][a-z_]
Để flag match được với đoạn regex này thì flag sẽ có kí tự in thường và in hoa xen kẽ:
Flag:
HCMUS-CTF{ThIs_iS_A_ReAlLy_bAsIc_rEv}