Android Crack 2023
【2023春节】解题领红包之三
APK:https://down.52pojie.cn/nUvaFj.7z|zYchSGxanOOx
用jadx反编译出来,其中关键是onCreate中的decrypt
public static final void m19onCreate$lambda0(MainActivity mainActivity, TextView textView, View view) {
Intrinsics.checkNotNullParameter(mainActivity, "this$0");
Intrinsics.checkNotNullParameter(textView, "$key");
MainActivity mainActivity2 = mainActivity;
mainActivity.jntm(mainActivity2);
textView.setText(String.valueOf(mainActivity.num));
if (mainActivity.check() == 999) {
Toast.makeText(mainActivity2, "快去论坛领CB吧!", 1).show();
textView.setText(mainActivity.decrypt("hnci}|jwfclkczkppkcpmwckng•", 2));
}
}
上面有两个部分,其中一是check结果为999,另一个就是解密那个字符串,check这个不需要传入参数,上面设定了get/set,这里直接修改判断即可。
public final int check() {
int i = this.num + 1;
this.num = i;
return i;
}
修改if-ne
为if-eq
00000036 invoke-virtual MainActivity->check()I, p0
0000003C move-result v0
0000003E const/16 v1, 999
00000042 if-ne v0, v1,
第二部分decrypt,这里
public final String decrypt(String str, int i) {
Intrinsics.checkNotNullParameter(str, "encryptTxt");
char[] charArray = str.toCharArray();
Intrinsics.checkNotNullExpressionValue(charArray, "this as java.lang.String).toCharArray()");
StringBuilder sb = new StringBuilder();
for (char c : charArray) {
sb.append((char) (c - i));
}
String sb2 = sb.toString();
Intrinsics.checkNotNullExpressionValue(sb2, "with(StringBuilder()) {\n… toString()\n }");
return sb2;
}
写个python脚本复原
#coding:utf-8
list_s = []
def decrypt(str1, int2):
for i in str1:
list_s.append(chr(ord(i) - int2))
return list_s
print("".join(decrypt("hnci}|jwfclkczkppkcpmwckng•", 2)))
当然,如果习惯用jeb打开的话就会发现,这个decrypt已经给我们复原好了。
【2023春节】解题领红包之四
APK:https://down.52pojie.cn/JfCdrX.7z | 5dPxREzsOa89
这个题需要知道自己的UID,反编译后可以看到主要的判断逻辑在onCreate
public static final void m19onCreate$lambda0(MainActivity mainActivity, View view) {
Intrinsics.checkNotNullParameter(mainActivity, "this$0");
A a = A.INSTANCE;
EditText editText = mainActivity.edit_uid;
EditText editText2 = null;
if (editText == null) {
Intrinsics.throwUninitializedPropertyAccessException("edit_uid");
editText = null;
}
String obj = StringsKt.trim((CharSequence) editText.getText().toString()).toString();
EditText editText3 = mainActivity.edit_flag;
if (editText3 == null) {
Intrinsics.throwUninitializedPropertyAccessException("edit_flag");
} else {
editText2 = editText3;
}
if (a.B(obj, StringsKt.trim((CharSequence) editText2.getText().toString()).toString())) {
Toast.makeText(mainActivity, "恭喜你,flag正确!", 1).show();
} else {
Toast.makeText(mainActivity, "flag错误哦,再想想!", 1).show();
}
}
这里,我们可以得到两个信息,第一个参数是UID,第二个参数是flag,参数传入A类下的B函数中。
public final boolean B(String str, String str2) {
Intrinsics.checkNotNullParameter(str, "str");
Intrinsics.checkNotNullParameter(str2, "str2");
if ((str.length() == 0 && str2.length() == 0) || !StringsKt.startsWith$default(str2, "flag{", false, 2, (Object) null) || !StringsKt.endsWith$default(str2, "}", false, 2, (Object) null)) {
return false;
}
String substring = str2.substring(5, str2.length() - 1);
Intrinsics.checkNotNullExpressionValue(substring, "this as java.lang.String…ing(startIndex, endIndex)");
C c = C.INSTANCE;
MD5Utils mD5Utils = MD5Utils.INSTANCE;
Base64Utils base64Utils = Base64Utils.INSTANCE;
String encode = B.encode(str + "Wuaipojie2023");
Intrinsics.checkNotNullExpressionValue(encode, "encode(str3)");
byte[] bytes = encode.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
return Intrinsics.areEqual(substring, c.cipher(mD5Utils.MD5(base64Utils.encodeToString(bytes)), 5));
}
上面的函数先对flag进行截取,去掉flag{},只留中间的字符串。然后使用B类下的encode进行处理UID,处理后再进行md5操作,然后跟输入进行比较,相同则代表输入正确,也就是我们需要获取的值。
public static String encode(String str) {
int length = str.length();
char[] cArr = new char[length];
int i = length - 1;
while (i >= 0) {
int i2 = i - 1;
cArr[i] = (char) (str.charAt(i) ^ '5');
if (i2 < 0) {
break;
}
i = i2 - 1;
cArr[i2] = (char) (str.charAt(i2) ^ '2');
}
return new String(cArr);
}
这个算法稍微比上面的麻烦一点,本来打算用添加log或者frida来做,发现手机的终端都不能安装,干脆直接把算法实现一遍,需要修改下面的UID为自己的UID。Base64Utils和MD5Utils也可以自己使用编码加密来替代算法。
import org.apache.commons.io.Charsets;
public class checkme {
public static String encode(String arg4) {
int v0 = arg4.length();
char[] v1 = new char[v0];
int v0_1 = v0 - 1;
while(v0_1 >= 0) {
int v2 = v0_1 - 1;
v1[v0_1] = (char)(arg4.charAt(v0_1) ^ 53);
if(v2 < 0) {
break;
}
v0_1 = v2 - 1;
v1[v2] = (char)(arg4.charAt(v2) ^ 50);
}
return new String(v1);
}
public static void main(String[] args) {
String v6 = encode(UID+"Wuaipojie2023");
byte[] v6_1 = v6.getBytes(Charsets.UTF_8);
String v6_2 = Base64Utils.INSTANCE.encodeToString(v6_1);
String v6_3 = MD5Utils.INSTANCE.MD5(v6_2);
System.out.println(C.INSTANCE.cipher(v6_3, 5));
}
}
最后算出来的值就是,再加上前后的flag{}。
flag{i4jkj66h8j7i4j7hi6ihf4h02hi062i4}
【2023春节】解题领红包之六
APK:https://down.52pojie.cn/cuKcNU.7z | my4OyfjP5HG2
反编译APK,发现是一个native层的解题,继续看下面的流程,需要把音量调到100-101之间,应该是需要让这个v2不等于0,既可把flag写入到本地的图片上
private final void Check_Volume(double arg6) {
TextView v0 = this.automedia;
if(v0 == null) {
Intrinsics.throwUninitializedPropertyAccessException("automedia");
v0 = null;
}
v0.setText(((CharSequence)("当前分贝:" + arg6)));
int v2 = 0;
if(Double.compare(84.0, arg6) <= 0 && arg6 <= 99.0) {
this.xigou(((Context)this));
return;
}
if(100.0 <= arg6 && arg6 <= 101.0) {
v2 = 1;
}
if(v2 != 0) {
Toast.makeText(((Context)this), "快去找flag吧", 1).show();
this.write_img();
}
}
其中的write_img函数为
private final void write_img() {
Closeable v2;
InputStream v1_1;
InputStream v0 = this.getAssets().open("aes.png");
Intrinsics.checkNotNullExpressionValue(v0, "assets.open(\"aes.png\")");
File v3 = new File(this.getPrivateDirectory(), "aes.png");
Closeable v0_1 = (Closeable)v0;
try {
v1_1 = (InputStream)v0_1;
v2 = (Closeable)new FileOutputStream(v3);
}
catch(Throwable v1) {
throw v1;
}
try {
ByteStreamsKt.copyTo$default(v1_1, ((OutputStream)(((FileOutputStream)v2))), 0, 2, null);
goto label_27;
}
catch(Throwable v1_2) {
}
try {
throw v1_2;
}
catch(Throwable v3_1) {
}
try {
CloseableKt.closeFinally(v2, v1_2);
throw v3_1;
label_27:
CloseableKt.closeFinally(v2, null);
goto label_34;
}
catch(Throwable v1) {
}
try {
throw v1;
}
catch(Throwable v2_1) {
}
CloseableKt.closeFinally(v0_1, v1);
throw v2_1;
label_34:
CloseableKt.closeFinally(v0_1, null);
}
其中在assert下面的aes图片是一个加密的字段,看起来不是写入里面,而是这个图片是显示了加密的flag,这里需要解密出来这个flag图片,大概?
反编译其中的lib52pj.so,发现其中存在JNI_Onload函数,里面貌似是调试自身的反调试和环境检测。
{
jint v3; // r4
int v4; // r0
int v6; // [sp+0h] [bp-18h] BYREF
ptrace(PTRACE_TRACEME, 0, 0, 0);
v6 = 0;
v3 = 65542;
if ( (*vm)->GetEnv(vm, &v6, 65542) )
return -1;
v4 = (*(*v6 + 24))(v6, "com/zj/wuaipojie2023_2/MainActivity");
if ( !v4 )
return -1;
if ( (*(*v6 + 860))(v6, v4, methods, 1) < 0 )
v3 = -1;
return v3;
}
这个APP的作用就是符合上面的条件后提供这个图片出来,除此之外都没有调用过native函数。
so的反编译这个用x86架构的,看起来清楚一些。其中_mm_add_epi8
代表SSE指令的8位加法,意思是r0=a0+b0,r1=a1+b1
,_mm_loadu_si128
代表加载128位的值,s1就是v3和xmmword_2A60相加。而xmmword_2A60的值查看Data是0xFBFEFBFEFBFEFBFEFBFEFBFEFBFEFBFE
,strcmp是比较,这里无实际意义。
bool __cdecl get_RealKey(_JNIEnv *a1, int a2, int a3)
{
const __m128i *v3; // esi
__m128i s1; // [esp+0h] [ebp-2Ch] BYREF
char v6; // [esp+10h] [ebp-1Ch]
unsigned int v7; // [esp+20h] [ebp-Ch]
v7 = __readgsdword(0x14u);
v3 = a1->functions->GetStringUTFChars(a1, a3, 0);
if ( strlen(v3->m128i_i8) != 16 )
return 0;
v6 = 0;
s1 = _mm_add_epi8(_mm_loadu_si128(v3), xmmword_2A60);
return strcmp(s1.m128i_i8, "thisiskey") != 0;
}
编写一个C脚本来还原key
#include <stdio.h>
int main() {
char key[] = "|wfkuqokj4548366";
char xmmword_2A60[] = { 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE, 0xFB, 0xFE};
int a = 0;
char newkey[32];
for (int i = 0; i < sizeof(key) - 1; i ++)
if (a % 2 == 0) {
newkey[i] = key[i] + xmmword_2A60[i];
} else {
newkey[i] = key[i] + xmmword_2A60[i + 1];
}
printf("%s", newkey);
}
运行的结果是wuaipojie2023114
,尝试解密png图片,解密出来是这样的一段值
89504E470D0A1A0A0000000D49484452000002E2000002E204030000006ECDAE0C0000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000030504C5445FEFEFEF6CF75F0BF5FF9DF89ECA34FF3F4EC272B24E26265E0DED98B867C52514EC29F6A6D380EB55633A8A7A6D0C7BCAE84814D000020004944415478DAEC5DBF6FDB481666F30057B77FD6B5AF1980D7C49D00BB484A42100CB81980BB07585DB4B48F4E75C6D9383BA51018D9FD032E5713。。。。。。。
利用脚本进行十六进制转图片操作
import binascii
payload = "89504E470D0A1A0A0000000D49484452000002E2000002E204030000006ECDAE0C0000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000030504C5445FEFEFEF6CF75F0BF5FF9DF89ECA34FF3F4EC272B24。。。。。。"
f=open("1.png","ab")
pic = binascii.a2b_hex(payload.encode())
f.write(pic)
f.close()
得到一个干杯的表情包。。。。用010editor打开查看一下。
可以看到开头是PNG的图片标志头,搜索一下看看是不是里面还带了什么zip或者图片。在1953行那还有一个PNG头,提取后面的图片。是一个1kb大小的二维码,扫码可得
flag{Happy_New_Year_Wuaipojie2023}
【2023春节】解题领红包之七
不要问,问就是不会,现在已经有大佬做出来了。可以看看大佬们的WP:https://www.52pojie.cn/thread-1742121-1-1.html