[羊城杯 2021]Ez_android
反编译APP
首先就是输入账号密码,正确后才能进入下一个函数的判断,但是这个getKeyAndRedirect需要联网获取key,才能进行下一步的加密验证。
获取到key之后就可以进行下一步计算
这里就是获取flag的关键代码了。
private boolean checkFlag(String arg3) {
return new String(EncodeUtils.encode(arg3.getBytes(StandardCharsets.UTF_8), false, this.key.getBytes(StandardCharsets.UTF_8))).equals("3lkHi9iZNK87qw0p6U391t92qlC5rwn5iFqyMFDl1t92qUnL6FQjqln76l-P");
}
也就是加密后需要输入的flag跟上面的字符串一致。这里的输入的账号密码很重要,因为需要后续输入密码来请求key,我们可以先过一遍流程,已知的账号为admin,加密后的密码为:c232666f1410b3f5010dc51cec341f58
。而这个字符串是md5加密后再每一位减1得到,也就是需要把上面的再加1。结果就是:c33367701511b4f6020ec61ded352059
。解密可以得到密码为:654321。
再把这个密码提交到平台上得到key:TGtUnkaJD0frq61uCQYw3-FxMiRvNOB/EWjgVcpKSzbs8yHZ257X9LldIeh4APom
需要计算出来的结果为:3lkHi9iZNK87qw0p6U391t92qlC5rwn5iFqyMFDl1t92qUnL6FQjqln76l-P
这开头熟悉的乘除法和下面的计算过程,应该算法是base64的编码过程,其中key就是替换了原本的字符。
可以从Java中把base64解码的代码抠出来本地执行,代码过长不贴出来了,运行后显示如下,需要把前缀进行替换。
[CISCN 2022 东北]crackme_Android
找到onCreate,onClick在其中,那就直接找关键函数,flag长38位,去掉前后的位数,中间的字符为32位,且需要把每四位进行一次看似是md5的加密,最后拼接的结果为:8393931a16db5a00f464a24abe24b17a9040b57d9cb2cbfa6bdc61d12e9b51f2789e8a8ae9406c969118e75e9bc65c4327fbc7c3accdf2c54675b0ddf3e0a6099b1b81046d525495e3a14ff6eae76eddfa1740cd6bd483da0f7684b2e4ec84b371f07bf95f0113eefab12552181dd832af8d1eb220186400c494db7091e402b0
md5算法中的传参是字符的每四位
先把上面的字符串分割为8段解密
8393931a16db5a00f464a24abe24b17a //4aea
9040b57d9cb2cbfa6bdc61d12e9b51f2 //146e
789e8a8ae9406c969118e75e9bc65c43 //9dc7
27fbc7c3accdf2c54675b0ddf3e0a609 //365e
9b1b81046d525495e3a14ff6eae76edd //4ec9
fa1740cd6bd483da0f7684b2e4ec84b3 //31f5
71f07bf95f0113eefab12552181dd832 //4728
af8d1eb220186400c494db7091e402b0 //4822
再把解密出来的拼接,加上flag的前缀即可。
[鹏城杯 2022]baby_re
反编译后发现是一个JNI的题,需要传入一个chararray的值
并且这个值再亦或后需要等于[0x77, 9, 40, 44, 106, 83, 0x7E, 0x7B, 33, 87, 0x71, 0x7B, 0x70, 93, 0x7D, 0x7F, 41, 82, 44, 0x7F, 39, 3, 0x7E, 0x7D, 0x77, 87, 0x2F, 0x7D, 33, 6, 44, 0x7F, 0x70, 0, 0x7E, 0x7B, 0x73, 24]
直接拖到IDA里面,找到调用函数,这个函数只有按位异或key这一个操作,但是key不知道,v5是传入的array数组。v6是数组长度。
点击查找,发现key是一个int类型的4位数组,第一个值0x56,函数列里有一个hide_key,这个函数是init_array内加载,就是给了一个key的原始数组,再进行异或替换。
key原始值是:[0x56,0x57,0x58,0x59]
key异或后的值是:[0x11, 0x65, 0x49, 0x4b]
编写脚本还原,上面的*(v5 + 4 * i)并不代表参数加值,而且指针变量,存储是地址值,int是4字节。
out = [0x77, 9, 40, 44, 106, 83, 0x7E, 0x7B, 33, 87, 0x71, 0x7B, 0x70, 93, 0x7D, 0x7F, 41, 82, 44, 0x7F, 39, 3, 0x7E, 0x7D, 0x77, 87, 0x2F, 0x7D, 33, 6, 44, 0x7F, 0x70, 0, 0x7E, 0x7B, 0x73, 24];
key = [0x11, 0x65, 0x49, 0x4b]
a = ''
for i in range(0, len(out)):
c = key[i % 4] ^ out[i]
a = a + chr(c)
print(a)
结果是:flag{6700280a84487e46f76f2f60ce4ae70b}
[HGAME 2022 week1]flagchecker
反编译后查看内容,是一个需要进行RC4加密,然后再进行base64编码输出的过程。
解密代码
#coding:utf-8
from Crypto.Cipher import ARC4
import base64
def rc4_decrypt(key, data):
cipher = ARC4.new(key)
return cipher.decrypt(base64.b64decode(data))
key = b'carol'
data = b'mg6CITV6GEaFDTYnObFmENOAVjKcQmGncF90WhqvCFyhhsyqq1s='
decrypted_data = rc4_decrypt(key, data)
print(decrypted_data)
[SWPU 2019]ThousandYearsAgo
反编译APP失败,解压缩发现dex异常,在res下发现所谓的真正的APP,一个txt文件,但是是压缩包,打开发现是APP目录格式,修改后缀打开即可。
Java层没有发现有用的东西,jni层调用了两个函数,反编译libnative-lib.so。
jni内有JNI_Onload函数,其中做了一个函数调用的判断。检查是否能获取到环境变量,然后再去检查是否存在MainActivity,然后调用stringFromJNI。
其中还有一个StringFromJNI,这个函数就是一个提示,会返回flag是flag{WeLcome_to-SWPU}}加密的结果
。
直接还原方法stringFromJNI
#include <stdio.h>
#include <string.h>
int main() {
size_t i;
char a1[35] = "flag{WeLcome_to-SWPU}}";
int a2 = 5;
for ( i = 0; i < strlen(a1); ++i ) {
if ( a1[i] < 0x41 || a1[i] > 0x5A ) {
if ( a1[i] >= 0x61 && a1[i] <= 0x7A )
a1[i] = (a1[i] + a2 - 97) % 26 + 97;
} else {
a1[i] = (a1[i] + a2 - 65) % 26 + 65;
}
}
printf(a1);
return 0;
}
结果是:kqfl{BjQhtrj_yt-XBUZ}},再加上flag的前后缀即可。
[鹤城杯 2021]AreYouRich
反编译发现是一个计算过程,这个关于账号密码没有提示,比如账号qweasdzxcr
,计算出来的密码就是qweasdzxcr_SUGCQFXZAP@001
在UserActivity内,有一个判断余额是否大于499999999的计算,大于则购买成功返回flag,还原后是flag{~1fHrTY8Y@_61$H*rPf6n3y!!}
,但是这个flag并不正确,说明token不对。
public static void main(String[] args) throws Exception {
byte[] arr_b = {0x40, 0x30, 0x30, 49};
byte[] arr_b1 = "qweasdzxcr".getBytes();
for(int v = 0; v < arr_b1.length; ++v) {
arr_b1[v] = (byte)(arr_b1[v] ^ 34);
}
String p = new String(arr_b1) + new String(arr_b);
String ss = "qweasdzxcr" + "_" + p + "_" + System.currentTimeMillis();
System.out.println(ss);
String s;
byte[] arr_b0 = {102, 108, 97, 103, 0x7B};
byte[] arr_b11 = {0x7D};
byte[] arr_b22 = new byte[]{15, 70, 3, 41, 1, 0x30, 35, 0x40, 58, 50, 0, 101, 100, 99, 11, 0x7B, 52, 8, 60, 0x77, 62, 0x73, 73, 17, 16};
byte[] arr_b3 = ss.getBytes();
if(25 > arr_b3.length) {
s = "";
}
else {
for(int v = 0; v < 25; ++v) {
arr_b22[v] = (byte)(arr_b22[v] ^ arr_b3[v]);
}
s = new String(arr_b0) + new String(arr_b22) + new String(arr_b11);
}
System.out.println(s);
}
然后再去查看另一个计算余额的过程,发现其中有涉及到token,就是账号密码的组合值,这个token至少是一个固定值,才可以正确计算出flag,先逆推出token。
public static void main(String[] args) throws Exception {
int[] arr_v = new int[1];
byte[] arr_bq = new byte[]{81, -13, 84, -110, 72, 77, (byte)0xA0, 77, 0x20, (byte)0x8D, -75, -38, -97, 69, (byte)0xC0, 49, 8, -27, 56, 0x72, -68, -82, 76, -106, -34};
byte[] arr_b2 = "5FQ5AaBGbqLGfYwjaRAuWGdDvyjbX5nH".getBytes();
byte[] arr_b3 = new byte[0x100];
for(int v = 0; v < 0x100; ++v) {
arr_b3[v] = (byte)v;
}
if(arr_b2.length == 0) {
arr_b3 = null;
}
else {
int v2 = 0;
int v3 = 0;
for(int v1 = 0; v1 < 0x100; ++v1) {
v3 = (arr_b2[v2] & 0xFF) + (arr_b3[v1] & 0xFF) + v3 & 0xFF;
byte b = arr_b3[v1];
arr_b3[v1] = arr_b3[v3];
arr_b3[v3] = b;
v2 = (v2 + 1) % arr_b2.length;
}
}
String string;
int v4 = Math.min(25, arr_bq.length);
int v5 = 16;
int v7 = 0;
int v8 = 0;
char[] chrCharArray = new char[25];
for(int v6 = 0; v6 < v4; ++v6) {
v7 = v7 + 1 & 0xFF;
v8 = (arr_b3[v7] & 0xFF) + v8 & 0xFF;
byte b1 = arr_b3[v7];
arr_b3[v7] = arr_b3[v8];
arr_b3[v8] = b1;
chrCharArray[v6] = (char) (arr_b3[(arr_b3[v7] & 0xFF) + (arr_b3[v8] & 0xFF) & 0xFF] ^ arr_bq[v6]);
}
System.out.println(chrCharArray);
}
得到的token为:vvvvipuser_TTTTKRWQGP@001
,账号密码就出来了,再把这个token放进去计算flag。
public static void main(String[] args) throws Exception {
String s;
byte[] arr_b0 = {102, 108, 97, 103, 0x7B};
byte[] arr_b11 = {0x7D};
byte[] arr_b22 = new byte[]{15, 70, 3, 41, 1, 0x30, 35, 0x40, 58, 50, 0, 101, 100, 99, 11, 0x7B, 52, 8, 60, 0x77, 62, 0x73, 73, 17, 16};
byte[] arr_b3 = "vvvvipuser_TTTTKRWQGP@001".getBytes();
if(25 > arr_b3.length) {
s = "";
}
else {
for(int v = 0; v < 25; ++v) {
arr_b22[v] = (byte)(arr_b22[v] ^ arr_b3[v]);
}
s = new String(arr_b0) + new String(arr_b22) + new String(arr_b11);
}
System.out.println(s);
}
得到flag:flag{y0u_h@V3_@_107_0f_m0n3y!!}
[鹤城杯 2021]designEachStep
这个APP流程有点麻烦,但不难,就是需要把输入的字符串分为三部分,每8个字符跟特定的数据比对,相同则下一步,也就是可以跟据特定的数据来获取那8个字符串,首先是data.bin文件前8个字节,然后是des解密后再用Inflater压缩获取前8位跟输入字符串的中间8位一致,des再解密后LZ4解压缩获取最后字符串的8位。反编译代码太长这里不贴,直接写计算代码:
下面的data是我手动解压后,不然还需要再进行一次解压缩。
public static void getEncodeStr() throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
byte[] des_decry;
byte[] arr_b14;
byte[] arr_b10 = new byte[0];
byte[] arr_b3 = null;
try (FileInputStream fis = new FileInputStream("E:\\Java_File\\File\\src\\data")) {
byte[] header = new byte[8];
int bytesRead = fis.read(header, 0, 8);
byte[] data = new byte[fis.available()];
bytesRead = fis.read(data);
Cipher cipher0 = Cipher.getInstance("DES");
cipher0.init(2, j(header));
des_decry = cipher0.doFinal(data);
Inflater inflater = new Inflater();
inflater.setInput(des_decry);
ByteArrayOutputStream byteArrayOutputStream1 = new ByteArrayOutputStream(des_decry.length);
try {
byte[] des_decry2 = new byte[0x400];
while (!inflater.finished()) {
byteArrayOutputStream1.write(des_decry2, 0, inflater.inflate(des_decry2));
}
byteArrayOutputStream1.close();
} catch (Exception exception0) {
System.out.println("error");
}
byte[] output = new byte[des_decry.length];
int resultLength = inflater.inflate(output);
inflater.end();
byte[] result = byteArrayOutputStream1.toByteArray();
byte[] arr_b12 = new byte[0];
if (result.length >= 8) {
arr_b12 = new byte[8];
System.arraycopy(result, 0, arr_b12, 0, 8);
byte[] arr_b13 = new byte[result.length - 8];
System.arraycopy(result, 8, arr_b13, 0, result.length - 8);
Cipher cipher1 = Cipher.getInstance("DES");
cipher1.init(2, j(arr_b12));
arr_b14 = cipher1.doFinal(arr_b13);
arr_b10 = arr_b14;
}
LZ4SafeDecompressor lZ4SafeDecompressor0 = LZ4Factory.safeInstance().safeDecompressor();
byte[] arr_b15 = new byte[arr_b10.length * 5];
int v1 = lZ4SafeDecompressor0.decompress(arr_b10, 0, arr_b10.length, arr_b15, 0);
byte[] arr_b16 = new byte[v1];
System.arraycopy(arr_b15, 0, arr_b16, 0, v1);
byte[] arr_b17 = new byte[0];
if (v1 >= 8) {
arr_b17 = new byte[8];
System.arraycopy(arr_b16, 0, arr_b17, 0, 8);
int v2 = v1 - 8;
byte[] arr_b18 = new byte[v2];
System.arraycopy(arr_b16, 8, arr_b18, 0, v2);
Cipher cipher2 = Cipher.getInstance("DES");
cipher2.init(2, j(arr_b17));
arr_b3 = cipher2.doFinal(arr_b18);
}
byte[] m = mergeBytes(header, arr_b12, arr_b17);
System.out.println(new String(m));
} catch (DataFormatException e) {
throw new RuntimeException(e);
}
}
public static byte[] mergeBytes(byte[]... bytes) {
int length = 0;
for (byte[] b : bytes) {
length += b.length;
}
byte[] result = new byte[length];
int destPos = 0;
for (byte[] b : bytes) {
System.arraycopy(b, 0, result, destPos, b.length);
destPos += b.length;
}
return result;
}
public static Key j(byte[] arr_b) {
byte[] arr_b1 = new byte[8];
for(int v = 0; v < arr_b.length && v < 8; ++v) {
arr_b1[v] = arr_b[v];
}
return new SecretKeySpec(arr_b1, "DES");
}
得到flag的值:DE5_c0mpr355_m@y_c0nfu53
[GFCTF 2021]easy_low
反编译得到一个类似账号密码判断的流程,但是需要逆推出
输入的账号也就是字符串s进行encode操作
字符串计算前后相等,这样可以爆破出账号s,得到结果为:LOHILMNMLKHILKHI
public static int encode(byte[] arr_b) {
byte[] arr_b2 = new byte[16];
byte[] ast = "qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM".getBytes();
for(int v1 = 0; v1 < 16; ++v1) {
for(int x = 0; x < ast.length; ++x) {
arr_b2[v1] = (byte)((ast[x] + arr_b[v1]) % 61);
arr_b2[v1] = (byte)(arr_b2[v1] * 2 - v1);
if (arr_b2[v1] == ast[x]) {
System.out.println((char) arr_b2[v1]);
}
} //LOHILMNMLKHILKHI
}
return 0;
}
然后再去计算密码,可得到结果:nmjknoloni_@0011
public static void main(String[] args) throws Exception {
byte[] arr_b = {0x40, 0x30, 0x30, 49, 49};
byte[] arr_b1 = encode("LOHILMNMLKHILKHI", new byte[]{23, 22, 26, 26, 25, 25, 25, 26, 27, 28, 30, 30, 29, 30, 0x20, 0x20});
for(int v = 0; v < arr_b1.length; ++v) {
arr_b1[v] = (byte)(arr_b1[v] ^ 34);
}
char[] arr_c = new String(arr_b1).toCharArray();
String s2 = "";
for(int v1 = 0; v1 < 10; ++v1) {
s2 = s2 + arr_c[v1];
}
System.out.println(s2 + "_" + new String(arr_b)); //nmjknoloni_@0011
}
然后找到b类,这个类应该才是最后的验证,其中有个f0.oho
,这个是JNI层函数调用,这里的O().v
跟上面的计算过程一样,都是encode的过程。所以输入的参数为:密码,用户名,拼接字段。
这里直接贴最后的WP,不要问,问就是没还原出来。。。
#include <bits/stdc++.h>
using namespace std;
int main() {
char key1[]={"nNjLnHlL"};
int count=1;
int key[7];//nNjLnHlL
for (int i = 0; i < 6; ++i) {
key[i] = key1[count];
count++;
}
unsigned int ks[6]={0x5d950ef2,0x86cca2de,0xc039bbf4,0xc5948102,0xaed55e9c,0x89f14377};
unsigned int k=0,bk=0;
unsigned int p[4];
for(int i=5;i>=0;i--)
if(i>0) ks[i]^=ks[i-1];
for(int i=0;i<24;i+=4){
k=ks[i/4];
k=(1<<key[i/4])^k;
k=((k>>16)) | ((~(k<<16))&0xffff0000);
k=((k<<key[i/4])) | (k>>(32-key[i/4]));
for(int j=0; j<4; j++) printf("%c", *((char*)&k+3-j));
}
return 0;
}
结果就是:WelCOme_To_mAkaBakA!BrO!