apktool d Vault101-1.1-release.apk
d2j-dex2jar.bat -f Vault101-1.1-release.apk
Java 코드 분석 위해 classes.dex → jar로 변환
apk 파일을 unzip해서 얻은 classes.dex 파일을 대상으로 dex2jar 디컴파일러로 jar을 얻을 수도 있다.
JD-GUI 또는 jadx 툴을 사용하여 Java 코드를 확인했다.
분석하던 중 jd-gui로 제대로 디컴파일이 안 된 코드들이 있어서 jadx를 병행해서 사용했다.
처음 실행 시, Flag를 입력하는 EditText 필드가 있다.
<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="29" android:compileSdkVersionCodename="10" package="com.sctf2020.vault101" platformBuildVersionCode="29" platformBuildVersionName="10">
<application android:allowBackup="false" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:extractNativeLibs="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:name="com.sctf2020.vault101.VaultApplication" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<service android:enabled="true" android:exported="true" android:name="com.sctf2020.vault101.VaultService"/>
<activity android:name="com.sctf2020.vault101.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
public void onClick(View view) {
try {
boolean a2 = this.s.a(this.p.getText().toString());
Toast toast = new Toast(this);
toast.setView(getLayoutInflater().inflate(a2 ? R.layout.toast_success_layout : R.layout.toast_fail_layout, (ViewGroup) findViewById(R.id.custom_toast_container)));
toast.setGravity(17, 0, 0);
toast.setDuration(1);
toast.show();
if (!a2) {
this.p.getText().clear();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
this.s.a 함수 코드에 접근할 방법을 찾아보자.
public class MainActivity extends e implements View.OnClickListener {
public EditText p;
public Button q;
public AnimatedVectorDrawable r;
public volatile b.c.a.b s;
public ServiceConnection t = new a();
public static class a implements b {
public IBinder a;
public a(IBinder param2IBinder) {
this.a = param2IBinder;
}
public boolean a(String param2String) {
Parcel parcel1 = Parcel.obtain();
Parcel parcel2 = Parcel.obtain();
try {
parcel1.writeInterfaceToken("com.sctf2020.vault101.IVault");
parcel1.writeString(param2String);
IBinder iBinder = this.a;
boolean bool = false;
iBinder.transact(1, parcel1, parcel2, 0);
parcel2.readException();
int i = parcel2.readInt();
if (i != 0)
bool = true;
return bool;
} finally {
parcel2.recycle();
parcel1.recycle();
}
}
public IBinder asBinder() {
return this.a;
}
}
}
public static class a implements b {
public IBinder a;
public a(IBinder param1IBinder) {
this.a = param1IBinder;
}
public boolean a(String param1String) {
Parcel parcel1 = Parcel.obtain();
Parcel parcel2 = Parcel.obtain();
try {
parcel1.writeInterfaceToken("com.sctf2020.vault101.IVault");
parcel1.writeString(param1String);
IBinder iBinder = this.a;
boolean bool = false;
iBinder.transact(1, parcel1, parcel2, 0);
parcel2.readException();
int i = parcel2.readInt();
if (i != 0)
bool = true;
return bool;
} finally {
parcel2.recycle();
parcel1.recycle();
}
}
public IBinder asBinder() {
return this.a;
}
}
public boolean onTransact(int param1Int1, Parcel param1Parcel1, Parcel param1Parcel2, int param1Int2) {
if (param1Int1 != 1) {
if (param1Int1 != 1598968902) // 1598968902 == -1
return super.onTransact(param1Int1, param1Parcel1, param1Parcel2, param1Int2);
param1Parcel2.writeString("com.sctf2020.vault101.IVault");
return true;
}
param1Parcel1.enforceInterface("com.sctf2020.vault101.IVault");
boolean bool = a(param1Parcel1.readString());
param1Parcel2.writeNoException();
param1Parcel2.writeInt(bool);
return true;
}
// com.sctf2020.vault101.VaultService
@Override // b.c.a.b
public boolean a(String str) {
try {
int i = this.f874a + 1;
this.f874a = i;
if (i > 3) {
Class.forName(c.d(";È\u0003p¯
4ŶorÂ\"Ý\u0010|", -500953648)).getMethod(c.d("qó%", 991422357), (Class) Class.forName(c.d("~jxe\u0005reíY:Bè`niaY", 1069257791)).getDeclaredField(c.d("\u0001ò¬\u0010", 1659367412)).get(null)).invoke(null, 0);
return false;
} else if (str == null) {
return false;
} else {
byte[] b2 = b.c.a.a.b((byte[]) Class.forName(c.d(".®$\u000fß1Ç\u0003?Ú6ʶ\"", 1451800421)).getMethod(c.d("7Ì£\u0002rØ0X", -552283301), new Class[0]).invoke(str, new Object[0]));
Object invoke = Class.forName(c.d("aogrfle¯}qjì.Cbsl35", 823239689)).getMethod(c.d("$OX{Í\u0010", -2050089752), Class.forName(c.d("\u000eé", 937562454)), (Class) Class.forName(c.d(";HCp¯\u0005tå¶ohõ%LRtó", -730536752)).getDeclaredField(c.d("\u0014ø¡\u0014", -1215097919)).get(null)).invoke(null, b2, Class.forName(c.d("pç\u000bfÆ´!\rÌ!BZ?Ë\u000egÌëq", -1393972808)).getDeclaredField(c.d("\u001aä\u0017Yï\u0004", 1778992991)).get(null));
Object newInstance = Class.forName(c.d("~jxe\u0005remY:Xrfb`c", 1356052543)).getConstructor(Class.forName(c.d("[C", 591904395))).newInstance(invoke);
Object invoke2 = Class.forName(c.d("$Í\u001efÆ4\u0007:EH Í\u000e:ê>]-_", -248372756)).getMethod(c.d("&DÇ\u0003ÿ\u0016xg¬", 64103114), (Class) Class.forName(c.d("/BNt\u001fqç`ó1F_pÓ", -401453852)).getDeclaredField(c.d("\u0001ò\u0004D", 195131734)).get(null)).invoke(VaultService.this, Integer.valueOf((int) R.string.magic));
return ((Boolean) Class.forName(c.d("zhs`-ddmu.Rwb`kf", -754317293)).getMethod(c.d("tø\u001auÅ®", 528601528), Class.forName(c.d("oâ5
º!OÈzä\u0016oæ ", -1620091986))).invoke(invoke2, newInstance)).booleanValue();
}
} catch (Throwable unused) {
throw new RuntimeException();
}
}
package b.c.a;
public class a {
/* renamed from: a reason: collision with root package name */
public static byte[] f821a;
public static byte[] a(byte[] bArr) {
try {
Object invoke = Class.forName(c.d(">JÂ0ûDwù¯0Õ´zhÝ!ë\u000ff", -989022505)).getMethod(c.d("rî:MGì0BSvn", -2055632580), Class.forName(c.d("+À00G¤nò\r3È6", 675347394))).invoke(null, c.d("QLÜkh^F,j_È\u0015%Yî Oukd", -1263812037));
Object newInstance = Class.forName(c.d("zè3`y§&QiquL>ú5db§\u0016FssdW[ì<Rqì&", -451507951)).getConstructor(Class.forName(c.d("KË", -867135215)), Class.forName(c.d(":È1¥4Žjð\"À7", 336766939))).newInstance(a.class.getDeclaredFields()[0].get(null), c.d("\u0005fã", -930415931));
Object newInstance2 = Class.forName(c.d("obVpûlcsþ0U~+pPtàlIw×!SphfTtñ\u0011pdä", -768401274)).getConstructor(Class.forName(c.d("\u000eé", 194738646))).newInstance(a.class.getDeclaredFields()[0].get(null));
Object obj = Class.forName(c.d("*À§ {2[;põ\u0006nâ¸1kÅ#", -822830269)).getDeclaredField(c.d("\u0014lþ\u0007ú(AT¿\u001fí\u001a", 1548356339)).get(null);
Class.forName(c.d(";È!aq6Ù uwÅê>paÈ'", 370203992)).getMethod(c.d("lmiu", 1209022468), (Class) Class.forName(c.d(";È)e\u0005Ð4Å\u001c:BÀ%Ì8aY", -341371526)).getDeclaredField(c.d("AR|P", 1490185396)).get(null), Class.forName(c.d("kà\u0017p¯²$Bô3HEx¯*tø", -1361421952)), Class.forName(c.d("nbÞ4 ebÑ#À!}-Û%Ä0.@È6Æ'mwÀ8ñ2r`É4Ý0vPØ0Â", -702764379))).invoke(invoke, obj, newInstance, newInstance2);
return (byte[]) Class.forName(c.d("jú$Y5gqLaý(;Hå5I~v", 1547207220)).getMethod(c.d("0Dpyå\u000fx", 148803807), Class.forName(c.d("NI", -1487124972))).invoke(invoke, bArr);
} catch (Throwable unused) {
throw new RuntimeException();
}
}
public static byte[] b(byte[] bArr) {
try {
Object invoke = Class.forName(c.d(".Â$aq3[5uwGjà;paÂ\"", 427878733)).getMethod(c.d("wlû\rEoqbTwî", 498197051), Class.forName(c.d("+@g`/Mpg&/R}3Hf", -2117115840))).invoke(null, c.d("\u0004fa?Ê%S&\u000e`KpsStí\u000e~n", -497723188));
Object newInstance = Class.forName(c.d("+À%as2Û2twÇoÒ#eh\u0002Ì(vfÜ\nÄ*S{Á2", 1399586122)).getConstructor(Class.forName(c.d("NI", -1666818412)), Class.forName(c.d("?Ê\nt5EzK'Â\u0012r", 1345337844))).newInstance(a.class.getDeclaredFields()[0].get(null), c.d("\u0000ä\"", 1922633154));
Object newInstance2 = Class.forName(c.d("{hñ!s\"fqbt÷-?z÷%h\"LuKeñ#|ló%y_ufx", -66160102)).getConstructor(Class.forName(c.d("\u001aã", -1333368352))).newInstance(a.class.getDeclaredFields()[0].get(null));
Object obj = Class.forName(c.d("?JÊ4ÛTwy Ý0{hÕ%Ë\u001ff", -1437859082)).getDeclaredField(c.d("\u0014ç\u001cVpí\u0001t4ZOk", -990494344)).get(null);
Class.forName(c.d("~ê0aq¡'QeuwM:È/paê6", 1004120349)).getMethod(c.d("mmiu", 1589187589), (Class) Class.forName(c.d(":HÉ5\u0011te{â0$LØ1Û", -1773462791)).getDeclaredField(c.d("@Ò\u0005", 1348396831)).get(null), Class.forName(c.d("j`Uq¥7dâÿ6Jäy/huò", -1730593653)), Class.forName(c.d("nbta'teâywjô}-qpld.À`blòmwjmYfràa`wåvPrej", -779848691))).invoke(invoke, obj, newInstance, newInstance2);
return (byte[]) Class.forName(c.d(">Jê$[\u0014wy\u000f`ý zhõ5K_f", 813386359)).getMethod(c.d("qä(më(", -186753258), Class.forName(c.d("_Á", -251609689))).invoke(invoke, bArr);
} catch (Throwable unused) {
throw new RuntimeException();
}
}
}
하지만 두 메소드 모두 난독화되어 스트링을 확인할 수 없다.
c.d() 메소드에 어떤 스트링과 숫자를 인자로 전달하고 있는 것만 알 수 있다.
c.d() 메소드를 찾아보자.
package b.c.a;
public class c {
/* renamed from: a reason: collision with root package name */
public static int f823a;
public static char a(char c, int i) {
return (char) (c & ((1 << i) ^ 65535));
}
public static char b(char c, int i) {
return (char) (c | (1 << i));
}
public static char c(char c, int i) {
return (char) ((c & (1 << i)) >> i);
}
public static String d(CharSequence charSequence, int i) {
StringBuilder sb = new StringBuilder();
if (i == 0) {
return sb.toString();
}
for (int i2 = 0; i2 < charSequence.length(); i2++) {
char charAt = charSequence.charAt(i2);
char c = (char) (i >> (i2 % 4));
int i3 = i2 % 3;
if (i3 == 0) {
for (int i4 = 0; i4 < 8; i4 += 2) {
int c2 = c(charAt, i4) ^ c(c, i4);
if (c2 == 0) {
charAt = a(charAt, i4);
} else if (c2 == 1) {
charAt = b(charAt, i4);
}
}
} else if (i3 == 1) {
for (int i5 = 1; i5 < 8; i5 += 2) {
int c3 = c(charAt, i5) ^ c(c, i5);
if (c3 == 0) {
charAt = a(charAt, i5);
} else if (c3 == 1) {
charAt = b(charAt, i5);
}
}
} else if (i3 == 2) {
for (int i6 = 0; i6 < 8; i6++) {
int c4 = c(charAt, i6) ^ c(c, i6);
if (c4 == 0) {
charAt = a(charAt, i6);
} else if (c4 == 1) {
charAt = b(charAt, i6);
}
}
}
sb.append((char) (charAt ^ f823a));
}
return sb.toString();
}
}
package com.sctf2020.vault101;
import android.app.Application;
import b.c.a.c;
public class VaultApplication extends Application {
public void onCreate() {
super.onCreate();
c.f823a = 1;
}
}
public class vault_101 {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(d(";È\u0003p¯
4ŶorÂ\"Ý\u0010|", -500953648));
System.out.println(d("qó%", 991422357));
System.out.println(d("~jxe\u0005reíY:Bè`niaY", 1069257791));
System.out.println(d("\u0001ò¬\u0010", 1659367412));
System.out.println(d(".®$\u000fß1Ç\u0003?Ú6ʶ\"", 1451800421));
System.out.println(d("7Ì£\u0002rØ0X", -552283301));
System.out.println(d("aogrfle¯}qjì.Cbsl35", 823239689));
System.out.println(d("$OX{Í\u0010", -2050089752));
System.out.println(d("\u000eé", 937562454));
System.out.println(d(";HCp¯\u0005tå¶ohõ%LRtó", -730536752));
System.out.println(d("\u0014ø¡\u0014", -1215097919));
System.out.println(d("pç\u000bfÆ´!\rÌ!BZ?Ë\u000egÌëq", -1393972808));
System.out.println(d("\u001aä\u0017Yï\u0004", 1778992991));
System.out.println(d("~jxe\u0005remY:Xrfb`c", 1356052543));
System.out.println(d("[C", 591904395));
System.out.println(d("$Í\u001efÆ4\u0007:EH Í\u000e:ê>]-_", -248372756));
System.out.println(d("&DÇ\u0003ÿ\u0016xg¬", 64103114));
System.out.println(d("/BNt\u001fqç`ó1F_pÓ", -401453852));
System.out.println(d("\u0001ò\u0004D", 195131734));
System.out.println(d("zhs`-ddmu.Rwb`kf", -754317293));
System.out.println(d("tø\u001auÅ®", 528601528));
System.out.println(d("oâ5
º!OÈzä\u0016oæ ", -1620091986));
System.out.println(d("!Ï#î5£.Ï%Ï
ó\"$Ò5Ó4ò", 354045889));
System.out.println(d("#Fä\u0012uQyç#@ó»%Z", 1074255429));
System.out.println(d("ê1§£%Íúkêaî5û", -72994916));
System.out.println(d("\u0010zBE", 1821340751));
System.out.println(d("0Gqsn@q¥2noÿ4Ga/BF{ÿ4yu", 1132272720));
System.out.println(d("bf~VNeoô]wn÷", 1545475118));
System.out.println(d("k`Õ1¥(`oìjð$shÍ7", 25375370));
System.out.println(d("wã\u000ffè«", -375635523));
System.out.println(d("oâ6`/ï!Ob/HOqæ'ds", 1759650052));
System.out.println(d("\u0000rÌ\u0000", 575374967));
System.out.println(d(";HÁ1§\u0001te¾kð.#@Ù7", -158782760));
//System.out.println(d((CharSequence) d(";HÁ1§\u0001te¾kð.#@Ù7", -158782760)));
System.out.println(d("JË", 543344920));
System.out.println(d("uå&Ä· \rË Â\u001a:É'Îèp", 446383039));
System.out.println(d(" Fñ/mB", 825348685));
System.out.println(d("+À§ -Ì0Ç$.Ò3È¿&", 1167863618));
System.out.println(d("?ʨ%\u0007Ó5E\u001a;Â\u0000!ι![", -521211012));
System.out.println(d("\u0010zè\u0010", -1491966233));
System.out.println(d("$M^fÆ\u001et§!Bðka[gÌA$", -1048288020));
System.out.println(d("\nlÇ\u0012qs@", 282303079));
}
public static int INT=1;
public static char a(char c, int i) {
return (char) (c & ((1 << i) ^ 65535));
}
public static char b(char c, int i) {
return (char) (c | (1 << i));
}
public static char c(char c, int i) {
return (char) ((c & (1 << i)) >> i);
}
public static String d(CharSequence charSequence, int i) {
StringBuilder sb = new StringBuilder();
if (i == 0) {
return sb.toString();
}
for (int i2 = 0; i2 < charSequence.length(); i2++) {
char charAt = charSequence.charAt(i2);
char c = (char) (i >> (i2 % 4));
int i3 = i2 % 3;
if (i3 == 0) {
for (int i4 = 0; i4 < 8; i4 += 2) {
int c2 = c(charAt, i4) ^ c(c, i4);
if (c2 == 0) {
charAt = a(charAt, i4);
} else if (c2 == 1) {
charAt = b(charAt, i4);
}
}
} else if (i3 == 1) {
for (int i5 = 1; i5 < 8; i5 += 2) {
int c3 = c(charAt, i5) ^ c(c, i5);
if (c3 == 0) {
charAt = a(charAt, i5);
} else if (c3 == 1) {
charAt = b(charAt, i5);
}
}
} else if (i3 == 2) {
for (int i6 = 0; i6 < 8; i6++) {
int c4 = c(charAt, i6) ^ c(c, i6);
if (c4 == 0) {
charAt = a(charAt, i6);
} else if (c4 == 1) {
charAt = b(charAt, i6);
}
}
}
sb.append((char) (charAt ^ INT));
}
return sb.toString();
}
}
c.d() 메소드의 난독화 해제 코드를 이용하여 각 스트링을 사람이 읽을 수 있는 스트링으로 변환하는 코드를 구현했다.
위의 Java 코드를 실행하면 다음의 결과를 얻을 수 있다.
# OUTPUT
java.lang.System
exit
java.lang.Integer
TYPE
java.lang.String
getBytes
android.util.Base64
encode
[B
java.lang.Integer
TYPE
android.util.Base64
NO_WRAP
java.lang.String
[B
android.content.Context
getString
java.lang.Integer
TYPE
java.lang.String
equals
java.lang.Object
android.content.res.Resources
getStringArray
java.lang.Integer
TYPE
android.content.Context
getResources
java.lang.String
charAt
java.lang.Integer
TYPE
java.lang.String
[B
android.util.Base64
decode
java.lang.String
java.lang.Integer
TYPE
android.util.Base64
NO_WRAP
package com.sctf2020.vault101;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import b.c.a.b;
import b.c.a.c;
import java.lang.reflect.Array;
public class VaultService extends Service {
/* renamed from: b reason: collision with root package name */
public IBinder f873b = new b(null);
public class b extends b.a {
/* renamed from: a reason: collision with root package name */
public int f874a;
public b(a aVar) {
}
@Override // b.c.a.b
public boolean a(String str) {
try {
int i = this.f874a + 1;
this.f874a = i;
if (i > 3) {
Class.forName(java.lang.System).getMethod(exit, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, 0);
return false;
} else if (str == null) {
return false;
} else {
byte[] b2 = b.c.a.a.b((byte[]) Class.forName(java.lang.String).getMethod(getBytes, new Class[0]).invoke(str, new Object[0]));
Object invoke = Class.forName(android.util.Base64).getMethod(encode, Class.forName([B), (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, b2, Class.forName(android.util.Base64).getDeclaredField(NO_WRAP).get(null));
Object newInstance = Class.forName(java.lang.String).getConstructor(Class.forName([B)).newInstance(invoke);
Object invoke2 = Class.forName(android.content.Context).getMethod(getString, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(VaultService.this, Integer.valueOf((int) R.string.magic));
return ((Boolean) Class.forName(java.lang.String).getMethod(equals, Class.forName(java.lang.Object)).invoke(invoke2, newInstance)).booleanValue();
}
} catch (Throwable unused) {
throw new RuntimeException();
}
}
}
public IBinder onBind(Intent intent) {
return this.f873b;
}
public void onCreate() {
try {
Object invoke = Class.forName(android.content.res.Resources).getMethod(getStringArray, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(Class.forName(android.content.Context).getMethod(getResources, new Class[0]).invoke(this, new Object[0]), Integer.valueOf((int) R.array.kind_of_magic));
if (invoke != null) {
int length = Array.getLength(invoke);
byte[] bArr = new byte[length];
for (int i = 0; i < length; i++) {
bArr[i] = (byte) ((Character) Class.forName(java.lang.String).getMethod(charAt, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(c.d((CharSequence) Class.forName(java.lang.String).getConstructor(Class.forName([B)).newInstance(Class.forName(android.util.Base64).getMethod(decode, Class.forName(java.lang.String), (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, Array.get(invoke, i), Class.forName(android.util.Base64).getDeclaredField(NO_WRAP).get(null))), i ^ 137), 0)).charValue();
}
b.c.a.a.class.getDeclaredFields()[0].set(null, bArr);
return;
}
throw new NullPointerException();
} catch (Throwable unused) {
throw new RuntimeException();
}
}
}
VaultService 코드의 스트링을 구한 스트링으로 대체하면 위와 같다.
javax.crypto.Cipher
getInstance
java.lang.String
AES/CBC/PKCS5Padding
javax.crypto.spec.SecretKeySpec
[B
java.lang.String
AES
javax.crypto.spec.IvParameterSpec
[B
javax.crypto.Cipher
DECRYPT_MODE
javax.crypto.Cipher
init
java.lang.Integer
TYPE
java.security.Key
java.security.spec.AlgorithmParameterSpec
javax.crypto.Cipher
doFinal
[B
javax.crypto.Cipher
getInstance
java.lang.String
AES/CBC/PKCS5Padding
javax.crypto.spec.SecretKeySpec
[B
java.lang.String
AES
javax.crypto.spec.IvParameterSpec
[B
javax.crypto.Cipher
ENCRYPT_MODE
javax.crypto.Cipher
init
java.lang.Integer
TYPE
java.security.Key
java.security.spec.AlgorithmParameterSpec
javax.crypto.Cipher
doFinal
[B
----------------------------------------------------------------
javax.crypto.Cipher
getInstance
java.lang.String
AES/CBC/PKCS5Padding
javax.crypto.spec.SecretKeySpec
[B
java.lang.String
AES
javax.crypto.spec.IvParameterSpec
[B
javax.crypto.Cipher
ENCRYPT_MODE
javax.crypto.Cipher
init
java.lang.Integer
TYPE
java.security.Key
java.security.spec.AlgorithmParameterSpec
javax.crypto.Cipher
doFinal
[B
package b.c.a;
public class a {
/* renamed from: a reason: collision with root package name */
public static byte[] f821a;
public static byte[] a(byte[] bArr) {
try {
Object invoke = Class.forName(javax.crypto.Cipher).getMethod(getInstance, Class.forName(java.lang.String)).invoke(null, AES/CBC/PKCS5Padding);
Object newInstance = Class.forName(javax.crypto.spec.SecretKeySpec).getConstructor(Class.forName(c.d("KË", -867135215)), Class.forName(java.lang.String)).newInstance(a.class.getDeclaredFields()[0].get(null), AES);
Object newInstance2 = Class.forName(javax.crypto.spec.IvParameterSpec).getConstructor(Class.forName([B)).newInstance(a.class.getDeclaredFields()[0].get(null));
Object obj = Class.forName(javax.crypto.Cipher).getDeclaredField(DECRYPT_MODE).get(null);
Class.forName(javax.crypto.Cipher).getMethod(init, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null), Class.forName(java.security.Key), Class.forName(java.security.spec.AlgorithmParameterSpec)).invoke(invoke, obj, newInstance, newInstance2);
return (byte[]) Class.forName(javax.crypto.Cipher).getMethod(doFinal, Class.forName(c.d("NI", -1487124972)).invoke(invoke, bArr);
} catch (Throwable unused) {
throw new RuntimeException();
}
}
public static byte[] b(byte[] bArr) {
try {
Object invoke = Class.forName(javax.crypto.Cipher).getMethod(getInstance, Class.forName(java.lang.String)).invoke(null, AES/CBC/PKCS5Padding);
Object newInstance = Class.forName(javax.crypto.spec.SecretKeySpec).getConstructor(Class.forName("[B"), Class.forName(java.lang.String)).newInstance(a.class.getDeclaredFields()[0].get(null), AES);
Object newInstance2 = Class.forName(javax.crypto.spec.IvParameterSpec).getConstructor(Class.forName("[B")).newInstance(a.class.getDeclaredFields()[0].get(null));
Object obj = Class.forName(javax.crypto.Cipher).getDeclaredField(ENCRYPT_MODE).get(null);
Class.forName(javax.crypto.Cipher).getMethod(init, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null), Class.forName(java.security.Key), Class.forName(java.security.spec.AlgorithmParameterSpec)).invoke(invoke, obj, newInstance, newInstance2);
return (byte[]) Class.forName(javax.crypto.Cipher).getMethod(doFinal, Class.forName("[B")).invoke(invoke, bArr);
} catch (Throwable unused) {
throw new RuntimeException();
}
}
}
b.c.a.a 클래스는 위와 같다.
위의 코드는 다음과 같이 해석하면 된다.
Class.forName(javax.crypto.Cipher).getMethod(getInstance).invoke("AES/CBC/PKCS5Padding"); 는 다음과 동일하다.
Cipher.getInstance("AES/CBC/PKCS5Padding");
public void onClick(View view) {
try {
boolean a2 = this.s.a(this.p.getText().toString());
Toast toast = new Toast(this);
toast.setView(getLayoutInflater().inflate(a2 ? R.layout.toast_success_layout : R.layout.toast_fail_layout, (ViewGroup) findViewById(R.id.custom_toast_container)));
toast.setGravity(17, 0, 0);
toast.setDuration(1);
toast.show();
if (!a2) {
this.p.getText().clear();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override // a.j.a.e, androidx.activity.ComponentActivity, a.g.c.d, a.b.k.e
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
this.q = (Button) findViewById(R.id.button);
EditText editText = (EditText) findViewById(R.id.editText);
this.p = editText;
editText.addTextChangedListener(new b());
AnimatedVectorDrawable animatedVectorDrawable = (AnimatedVectorDrawable) ((ImageView) findViewById(R.id.imageView)).getDrawable();
this.r = animatedVectorDrawable;
animatedVectorDrawable.start();
bindService(new Intent(this, VaultService.class), this.t, 1);
}
@Override // b.c.a.b
public boolean a(String str) {
try {
int i = this.f874a + 1;
this.f874a = i;
if (i > 3) {
Class.forName(java.lang.System).getMethod(exit, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, 0);
return false;
} else if (str == null) {
return false;
} else {
byte[] b2 = b.c.a.a.b((byte[]) Class.forName(java.lang.String).getMethod(getBytes, new Class[0]).invoke(str, new Object[0]));
Object invoke = Class.forName(android.util.Base64).getMethod(encode, Class.forName("[B"), (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, b2, Class.forName(android.util.Base64).getDeclaredField(NO_WRAP).get(null));
Object newInstance = Class.forName(java.lang.String).getConstructor(Class.forName("[B")).newInstance(invoke);
Object invoke2 = Class.forName(android.content.Context).getMethod(getString, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(VaultService.this, Integer.valueOf((int) R.string.magic));
return ((Boolean) Class.forName(java.lang.String).getMethod(equals, Class.forName(java.lang.Object)).invoke(invoke2, newInstance)).booleanValue();
}
} catch (Throwable unused) {
throw new RuntimeException();
}
}
입력받은 스트링은 b.c.a.a. 클래스의 b 메소드 인자로 넘긴다. encrypt하기 위해서이다.
b.c.a.a 클래스의 b 메소드는 AES 암호화 알고리즘을 base64로 하드코딩된 key와 IV를 사용하여 스트링을 암호화한다.
public void onCreate() {
try {
Object invoke = Class.forName(android.content.res.Resources).getMethod(getStringArray, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(Class.forName(android.content.Context).getMethod(getResources, new Class[0]).invoke(this, new Object[0]), Integer.valueOf((int) R.array.kind_of_magic));
if (invoke != null) {
int length = Array.getLength(invoke);
byte[] bArr = new byte[length];
for (int i = 0; i < length; i++) {
bArr[i] = (byte) ((Character) Class.forName(java.lang.String).getMethod(charAt, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(c.d((CharSequence) Class.forName(java.lang.String).getConstructor(Class.forName("[B")).newInstance(Class.forName(android.util.Base64).getMethod(decode, Class.forName(java.lang.String), (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, Array.get(invoke, i), Class.forName(android.util.Base64).getDeclaredField(NO_WRAP).get(null))), i ^ 137), 0)).charValue();
}
b.c.a.a.class.getDeclaredFields()[0].set(null, bArr);
return;
}
throw new NullPointerException();
} catch (Throwable unused) {
throw new RuntimeException();
}
}
이때, AES-CBC 암호화 알고리즘의 key와 초기 IV는 VaultService의 onCreate에서 생성된다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="kind_of_magic">
<item>UEBxWw==</item>
<item>Sk5xVcOICw==</item>
<item>bnRX</item>
<item>S0BgWw==</item>
<item>Nw==</item>
<item>R0ZxRMOLElk=</item>
<item>TkJhWw==</item>
<item>dHZHdcOl</item>
<item>eWRNYQ==</item>
<item>bHRSeMOi</item>
<item>R05tVw==</item>
<item>d2hScA==</item>
<item>T0xyVMOADQ==</item>
<item>f2pQ</item>
<item>Q0xsVw==</item>
<item>Nw==</item>
</string-array>
</resources>
R.array.kind_of_magic 배열을 사용하는데, 디코딩한 파일에서 res\values\arrays.xml 파일에서 확인할 수 있다.
kind_of_magic의 각 원소는 base64로 인코딩되어 있고, 반복문에서 각 원소를 디코딩하여 첫 번째 문자를 bArr에 저장한다.
모든 bArr 내 문자들을 조합한 스트링이 key와 IV에 해당한다.
package b.c.a;
public class a {
/* renamed from: a reason: collision with root package name */
public static byte[] f821a;
public static byte[] a(byte[] bArr) {
try {
Object invoke = Class.forName(javax.crypto.Cipher).getMethod(getInstance, Class.forName(java.lang.String)).invoke(null, AES/CBC/PKCS5Padding);
Object newInstance = Class.forName(javax.crypto.spec.SecretKeySpec).getConstructor(Class.forName(c.d("KË", -867135215)), Class.forName(java.lang.String)).newInstance(a.class.getDeclaredFields()[0].get(null), AES);
Object newInstance2 = Class.forName(javax.crypto.spec.IvParameterSpec).getConstructor(Class.forName([B)).newInstance(a.class.getDeclaredFields()[0].get(null));
Object obj = Class.forName(javax.crypto.Cipher).getDeclaredField(DECRYPT_MODE).get(null);
Class.forName(javax.crypto.Cipher).getMethod(init, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null), Class.forName(java.security.Key), Class.forName(java.security.spec.AlgorithmParameterSpec)).invoke(invoke, obj, newInstance, newInstance2);
return (byte[]) Class.forName(javax.crypto.Cipher).getMethod(doFinal, Class.forName(c.d("NI", -1487124972)).invoke(invoke, bArr);
} catch (Throwable unused) {
throw new RuntimeException();
}
}
public static byte[] b(byte[] bArr) {
try {
Object invoke = Class.forName(javax.crypto.Cipher).getMethod(getInstance, Class.forName(java.lang.String)).invoke(null, AES/CBC/PKCS5Padding);
Object newInstance = Class.forName(javax.crypto.spec.SecretKeySpec).getConstructor(Class.forName("[B"), Class.forName(java.lang.String)).newInstance(a.class.getDeclaredFields()[0].get(null), AES);
Object newInstance2 = Class.forName(javax.crypto.spec.IvParameterSpec).getConstructor(Class.forName("[B")).newInstance(a.class.getDeclaredFields()[0].get(null));
Object obj = Class.forName(javax.crypto.Cipher).getDeclaredField(ENCRYPT_MODE).get(null);
Class.forName(javax.crypto.Cipher).getMethod(init, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null), Class.forName(java.security.Key), Class.forName(java.security.spec.AlgorithmParameterSpec)).invoke(invoke, obj, newInstance, newInstance2);
return (byte[]) Class.forName(javax.crypto.Cipher).getMethod(doFinal, Class.forName("[B")).invoke(invoke, bArr);
} catch (Throwable unused) {
throw new RuntimeException();
}
}
}
VaultService의 onCreate()에서 구한 key와 IV인 PKnJ3BJqymGvKzG2로 암호화를 진행한다.
Object invoke = Class.forName(android.util.Base64).getMethod(encode, Class.forName("[B"), (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, b2, Class.forName(android.util.Base64).getDeclaredField(NO_WRAP).get(null));
Object newInstance = Class.forName(java.lang.String).getConstructor(Class.forName("[B")).newInstance(invoke);
암호화 이후 해당 스트링을 Base64로 인코딩 후 스트링으로 변환한다.
Object invoke2 = Class.forName(android.content.Context).getMethod(getString, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(VaultService.this, Integer.valueOf((int) R.string.magic));
이후 R.string.magic으로 스트링을 가져온다.
<string name="magic">7E3Q5fm4lBSKXaHTnlCO52VL/iY6f+hQQ35oeFphtZIu3pf0QuOEpFB5nTeg8GTx</string>
해당 magic이란 이름을 가지는 스트링은 res\values\strings.xml 파일에서 확인할 수 있다.
return ((Boolean) Class.forName(java.lang.String).getMethod(equals, Class.forName(java.lang.Object)).invoke(invoke2, newInstance)).booleanValue();
입력한 값을 AES로 암호화한 후 Base64로 인코딩한 스트링과, magic 스트링이 동일한지(equals 메소드 사용)에 대한 boolean 값을 반환한다.
Flag는 다음의 과정으로 구할 수 있다.
1. magic을 Base64로 디코딩
2. b.c.a.a의 a 메소드를 이용하여 복호화
3. 2에서 얻은 스트링을 String 객체로 변환하여 출력하면 get flag!
import java.lang.reflect.Array;
import java.util.Base64;
public class flag_vault101 {
public static void main(String[] args) throws java.io.UnsupportedEncodingException, IllegalArgumentException, IllegalAccessException, SecurityException{
// TODO Auto-generated method stub
String kind_of_magic[] = {
"UEBxWw==",
"Sk5xVcOICw==",
"bnRX",
"S0BgWw==",
"Nw==",
"R0ZxRMOLElk=",
"TkJhWw==",
"dHZHdcOl",
"eWRNYQ==",
"bHRSeMOi",
"R05tVw==",
"d2hScA==",
"T0xyVMOADQ==",
"f2pQ",
"Q0xsVw==",
"Nw=="
};
String magic = "7E3Q5fm4lBSKXaHTnlCO52VL/iY6f+hQQ35oeFphtZIu3pf0QuOEpFB5nTeg8GTx";
int length = kind_of_magic.length;
byte[] bArr = new byte[length];
Base64.Decoder decode = Base64.getDecoder();
for(int i=0; i<length; i++) {
Object obj = Array.get(kind_of_magic, i);
byte[] invoke = decode.decode(obj.toString());
String str = d(new String(invoke, "UTF-8"), i ^ 137);
bArr[i] = ((byte) str.charAt(0));
}
a.class.getDeclaredFields()[0].set(null, bArr);
Base64.Decoder decoder = Base64.getDecoder();
byte[] magic_decode= decoder.decode(magic);
byte[] result = a.a(magic_decode);
System.out.println(new String(result));
}
public static int INT=1;
public static char a(char c, int i) {
return (char) (c & ((1 << i) ^ 65535));
}
public static char b(char c, int i) {
return (char) (c | (1 << i));
}
public static char c(char c, int i) {
return (char) ((c & (1 << i)) >> i);
}
public static String d(CharSequence charSequence, int i) {
StringBuilder sb = new StringBuilder();
if (i == 0) {
return sb.toString();
}
for (int i2 = 0; i2 < charSequence.length(); i2++) {
char charAt = charSequence.charAt(i2);
char c = (char) (i >> (i2 % 4));
int i3 = i2 % 3;
if (i3 == 0) {
for (int i4 = 0; i4 < 8; i4 += 2) {
int c2 = c(charAt, i4) ^ c(c, i4);
if (c2 == 0) {
charAt = a(charAt, i4);
} else if (c2 == 1) {
charAt = b(charAt, i4);
}
}
} else if (i3 == 1) {
for (int i5 = 1; i5 < 8; i5 += 2) {
int c3 = c(charAt, i5) ^ c(c, i5);
if (c3 == 0) {
charAt = a(charAt, i5);
} else if (c3 == 1) {
charAt = b(charAt, i5);
}
}
} else if (i3 == 2) {
for (int i6 = 0; i6 < 8; i6++) {
int c4 = c(charAt, i6) ^ c(c, i6);
if (c4 == 0) {
charAt = a(charAt, i6);
} else if (c4 == 1) {
charAt = b(charAt, i6);
}
}
}
sb.append((char) (charAt ^ INT));
}
return sb.toString();
}
}
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class a {
public static byte[] f821a;
public static byte[] a(byte[] bArr) {
try {
Cipher AES_CBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec((byte[])a.class.getDeclaredFields()[0].get(null), "AES");
IvParameterSpec IV = new IvParameterSpec((byte[])a.class.getDeclaredFields()[0].get(null));
AES_CBC.init(Cipher.DECRYPT_MODE, key, IV);
return (byte[]) AES_CBC.doFinal(bArr);
}catch (Throwable unused) {
throw new RuntimeException();
}
}
}
SCTF{53CUr17Y_7Hr0U6H_085CUr17Y_15_N07_3N0U6H}
Android device connection setting in ubuntu (0) | 2021.09.09 |
---|---|
Android Device Rooting (Magisk) (0) | 2021.09.09 |
[Android Malware] Anubis Analysis (0) | 2021.08.02 |
[Google CTF 2020] android (0) | 2021.07.21 |
[Android Malware] CovidLock analysis (0) | 2021.07.19 |