题目(来源:Jarvis-OJ):
Androideasy
使用APKToolBOX中的jadx打开该apk文件找到MainActivity查看主函数,如下所示
package com.a.sample.androidtest;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText editText;
private byte[] s = new byte[]{(byte) 113, (byte) 123, (byte) 118, (byte) 112, (byte) 108, (byte) 94, (byte) 99, (byte) 72, (byte) 38, (byte) 68, (byte) 72, (byte) 87, (byte) 89, (byte) 72, (byte) 36, (byte) 118, (byte) 100, (byte) 78, (byte) 72, (byte) 87, (byte) 121, (byte) 83, (byte) 101, (byte) 39, (byte) 62, (byte) 94, (byte) 62, (byte) 38, (byte) 107, (byte) 115, (byte) 106};
public boolean check() {
byte[] chars = this.editText.getText().toString().getBytes();
if (chars.length != this.s.length) {
return false;
}
int i = 0;
while (i < this.s.length && i < chars.length) {
if (this.s[i] != (chars[i] ^ 23)) {
return false;
}
i++;
}
return true;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((int) R.layout.activity_main);
final Context context = this;
this.editText = (EditText) findViewById(R.id.edit_text);
findViewById(R.id.button).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (MainActivity.this.check()) {
Toast.makeText(context, "You got the flag!", 1).show();
} else {
Toast.makeText(context, "Sorry your flag is wrong", 1).show();
}
}
});
}
}
查看onClick函数,其中调用了check函数,check函数中将用户输入的字符串与23进行异或,然后与数组s进行比较,判断其是否一致。所以我们可以写出flag脚本
s = [ 113, 123, 118, 112, 108, 94, 99, 72, 38, 68, 72, 87, 89, 72, 36, 118, 100, 78, 72, 87, 121, 83, 101, 39, 62, 94, 62, 38, 107, 115, 106 ] flag = "" for i in s: flag += chr(i^0x17) print flag
Flag: flag{It_1S_@N_3asY_@nDr0)I)1|d}
DD Android Easy
拖进APKToolBOX的jadx中,找到FlagActivity,如下所示
package com.didi_ctf.flagapp;
import android.os.Bundle;
import android.support.v7.a.d;
import android.view.View;
import android.widget.TextView;
public class FlagActivity extends d {
private static String m = "com.didi_ctf.flagapp.FlagActivity";
private static final byte[] p = new byte[]{(byte) -40, (byte) -62, (byte) 107, (byte) 66, (byte) -126, (byte) 103, (byte) -56, (byte) 77, (byte) 122, (byte) -107, (byte) -24, (byte) -127, (byte) 72, (byte) -63, (byte) -98, (byte) 64, (byte) -24, (byte) -5, (byte) -49, (byte) -26, (byte) 79, (byte) -70, (byte) -26, (byte) -81, (byte) 120, (byte) 25, (byte) 111, (byte) -100, (byte) -23, (byte) -9, (byte) 122, (byte) -35, (byte) 66, (byte) -50, (byte) -116, (byte) 3, (byte) -72, (byte) 102, (byte) -45, (byte) -85, (byte) 0, (byte) 126, (byte) -34, (byte) 62, (byte) 83, (byte) -34, (byte) 48, (byte) -111, (byte) 61, (byte) -9, (byte) -51, (byte) 114, (byte) 20, (byte) 81, (byte) -126, (byte) -18, (byte) 27, (byte) -115, (byte) -76, (byte) -116, (byte) -48, (byte) -118, (byte) -10, (byte) -102, (byte) -106, (byte) 113, (byte) -104, (byte) 98, (byte) -109, (byte) 74, (byte) 48, (byte) 47, (byte) -100, (byte) -88, (byte) 121, (byte) 22, (byte) -63, (byte) -32, (byte) -20, (byte) -41, (byte) -27, (byte) -20, (byte) -118, (byte) 100, (byte) -76, (byte) 70, (byte) -49, (byte) -39, (byte) -27, (byte) -106, (byte) -13, (byte) -108, (byte) 115, (byte) -87, (byte) -1, (byte) -22, (byte) -53, (byte) 21, (byte) -100, (byte) 124, (byte) -95, (byte) -40, (byte) 62, (byte) -69, (byte) 29, (byte) 56, (byte) -53, (byte) 85, (byte) -48, (byte) 25, (byte) 37, (byte) -78, (byte) 11, (byte) -110, (byte) -24, (byte) -120, (byte) -82, (byte) 6, (byte) -94, (byte) -101};
private static final byte[] q = new byte[]{(byte) -57, (byte) -90, (byte) 53, (byte) -71, (byte) -117, (byte) 98, (byte) 62, (byte) 98, (byte) 101, (byte) -96, (byte) 36, (byte) 110, (byte) 77, (byte) -83, (byte) -121, (byte) 2, (byte) -48, (byte) 94, (byte) -106, (byte) -56, (byte) -49, (byte) -80, (byte) -1, (byte) 83, (byte) 75, (byte) 66, (byte) -44, (byte) 74, (byte) 2, (byte) -36, (byte) -42, (byte) -103, (byte) 6, (byte) -115, (byte) -40, (byte) 69, (byte) -107, (byte) 85, (byte) -78, (byte) -49, (byte) 54, (byte) 78, (byte) -26, (byte) 15, (byte) 98, (byte) -70, (byte) 8, (byte) -90, (byte) 94, (byte) -61, (byte) -84, (byte) 64, (byte) 112, (byte) 51, (byte) -29, (byte) -34, (byte) 126, (byte) -21, (byte) -126, (byte) -71, (byte) -31, (byte) -24, (byte) -60, (byte) -2, (byte) -81, (byte) 66, (byte) -84, (byte) 85, (byte) -91, (byte) 10, (byte) 84, (byte) 70, (byte) -8, (byte) -63, (byte) 26, (byte) 126, (byte) -76, (byte) -104, (byte) -123, (byte) -71, (byte) -126, (byte) -62, (byte) -23, (byte) 11, (byte) -39, (byte) 70, (byte) 14, (byte) 59, (byte) -101, (byte) -39, (byte) -124, (byte) 91, (byte) -109, (byte) 102, (byte) -49, (byte) 21, (byte) 105, (byte) 0, (byte) 37, Byte.MIN_VALUE, (byte) -57, (byte) 117, (byte) 110, (byte) -115, (byte) -86, (byte) 56, (byte) 25, (byte) -46, (byte) -55, (byte) 7, (byte) -125, (byte) 109, (byte) 76, (byte) 104, (byte) -15, (byte) 82, (byte) -53, (byte) 18, (byte) -28, (byte) -24};
private TextView n;
private TextView o;
private String i() {
int i;
int i2 = 0;
byte[] bArr = new byte[p.length];
for (i = 0; i < bArr.length; i++) {
bArr[i] = (byte) (p[i] ^ q[i]);
}
byte b = bArr[0];
i = 0;
while (bArr[b + i] != (byte) 0) {
i++;
}
byte[] bArr2 = new byte[i];
while (i2 < i) {
bArr2[i2] = bArr[b + i2];
i2++;
}
return new String(bArr2);
}
public void onClickTest(View view) {
if (this.n.getText().toString().equals(i())) {
this.o.setText(R.string.flag_result_yes);
} else {
this.o.setText(R.string.flag_result_no);
}
}
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView((int) R.layout.activity_flag);
this.n = (TextView) findViewById(R.id.flag_entry);
this.o = (TextView) findViewById(R.id.flag_result);
}
}
这里首先就是将用户输入的字符串和i()函数的返回值进行对比,若正确,则说明用户输入的为flag,所以我们只要分析i()就可得到flag。查看函数后,首先该函数将两个数组进行异或,然后去第一个元素为首地址,一直往后读到0为止,这之间的字符串就是flag,下面是获得flag的脚本
p = [ -40, -62, 107, 66, -126, 103, -56, 77, 122, -107, -24, -127, 72, -63, -98, 64, -24, -5, -49, -26, 79, -70, -26, -81, 120, 25, 111, -100, -23, -9, 122, -35, 66, -50, -116, 3, -72, 102, -45, -85, 0, 126, -34, 62, 83, -34, 48, -111, 61, -9, -51, 114, 20, 81, -126, -18, 27, -115, -76, -116, -48, -118, -10, -102, -106, 113, -104, 98, -109, 74, 48, 47, -100, -88, 121, 22, -63, -32, -20, -41, -27, -20, -118, 100, -76, 70, -49, -39, -27, -106, -13, -108, 115, -87, -1, -22, -53, 21, -100, 124, -95, -40, 62, -69, 29, 56, -53, 85, -48, 25, 37, -78, 11, -110, -24, -120, -82, 6, -94, -101 ]
q = [-57, -90, 53, -71, -117, 98, 62, 98, 101, -96, 36, 110, 77, -83, -121, 2, -48, 94, -106, -56, -49, -80, -1, 83, 75, 66, -44, 74, 2, -36, -42, -103, 6, -115, -40, 69, -107, 85, -78, -49, 54, 78, -26, 15, 98, -70, 8, -90, 94, -61, -84, 64, 112, 51, -29, -34, 126, -21, -126, -71, -31, -24, -60, -2, -81, 66, -84, 85, -91, 10, 84, 70, -8, -63, 26, 126, -76, -104, -123, -71, -126, -62, -23, 11, -39, 70, 14, 59, -101, -39, -124, 91, -109, 102, -49, 21, 105, 0, 37, -128, -57, 117, 110, -115, -86, 56, 25, -46, -55, 7, -125, 109, 76, 104, -15, 82, -53, 18, -28, -24 ]
arr1 = []
flag = ""
for i in range(len(p)):
arr1.append(p[i]^q[i])
k = arr1[0]
i1 = 0
while 1:
if arr1[k+i1] == 0:
break
i1 += 1
for i in range(i1):
flag += chr(arr1[k+i])
print flag
Flag:DDCTF-3ad60811d87c4a2dba0ef651b2d93476@didichuxing.com
DD - Android Normal
首先使用APKToolBOX中的jadx打开apk文件,找到主函数,如下所示
发现只要用户输入的flag和stringFromJNI函数的返回值一致,则输出正确。又看到该函数来自hello-libs中,因此解压apk文件,在解压后的目录中(DD - Android Normal\DDCTF-Normal\lib\arm64-v8a)找到该库文件,并拖进IDA中即可找到该函数,如下所示:
__int64 __fastcall Java_com_didictf_hellolibs_MainActivity_stringFromJNI(__int64 a1)
{
__int64 v1; // x19
__int64 v2; // ST00_8
__int64 v3; // x20
unsigned int v4; // w21
__int64 v5; // x0
__int64 v6; // x8
signed int v7; // w10
unsigned __int8 *v8; // x11
unsigned int v9; // w9
int v10; // t1
int v11; // w20
__int64 v12; // x8
__int64 result; // x0
char v14[184]; // [xsp+8h] [xbp-1A8h]
__int128 v15; // [xsp+C0h] [xbp-F0h]
__int128 v16; // [xsp+D0h] [xbp-E0h]
__int128 v17; // [xsp+E0h] [xbp-D0h]
__int128 v18; // [xsp+F0h] [xbp-C0h]
__int128 v19; // [xsp+100h] [xbp-B0h]
__int128 v20; // [xsp+110h] [xbp-A0h]
__int128 v21; // [xsp+120h] [xbp-90h]
__int128 v22; // [xsp+130h] [xbp-80h]
__int128 v23; // [xsp+140h] [xbp-70h]
__int128 v24; // [xsp+150h] [xbp-60h]
__int64 v25; // [xsp+160h] [xbp-50h]
v1 = a1;
v2 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v3 = GetTicks();
v4 = 0;
do
gpower(v4++);
while ( v4 != 32 );
v5 = GetTicks();
__android_log_print(4LL, "hell-libs::", "calculation time: %lu", v5 - v3);
v15 = xmmword_A40;
v16 = xmmword_A50;
v17 = xmmword_A60;
v18 = xmmword_A70;
v21 = xmmword_AA0;
v22 = xmmword_AB0;
v6 = 0LL;
v19 = xmmword_A80;
v20 = xmmword_A90;
v23 = xmmword_AC0;
v24 = xmmword_AD0;
do
{
*(&v25 + v6) = byte_B96[v6 + 160] ^ byte_AE0[v6 + 160];
++v6;
}
while ( v6 != 22 );
v7 = -1;
v8 = &v15 + (v15 >> 1);
v9 = -2;
do
{
v10 = *v8++;
++v9;
++v7;
}
while ( v10 );
if ( v7 < 1 )
{
v12 = 0LL;
}
else
{
v11 = v9 + 1;
memcpy(v14, &v15 + (v15 >> 1), v9 + 1LL);
v12 = v11;
}
v14[v12] = 0;
result = (*(*v1 + 1336LL))(v1, v14);
*(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
return result;
}
可以看到这里的v15-v24是一连串非常大的数据,双击其中一个数据,来到一下界面
对这里的数据点击r,查看一下这些数据对应的字符,忽然发现这里隐藏着flag
尝试读取出来,在模拟器中运行程序,并提交该flag,提示正确
Flag:DDCTF-397a90a3267641658bbc975326700f4b@didichuxing.com
FindPass
拖进jadx中,找到主函数中的关键代码,如下所示
看到底部Flag==flag{Key},我们知道上面的那个if就必须为真,接着找到fkey,就在最开始的位置,可以看到它应该是用户输入的字符串,而ekey则就是R.string.fkey这个字符串
这里只有一个ID号
接下来我们把APK拖进Android Killer这个软件,因为它可以对整个工程进行字符串搜索,在搜索框中输入fkey
然后我们在string.xml中找到该字符串,为“Tr43Fla92Ch4n93”,继续看接下来的代码
这里它读取了src.jpg中的每个字节到数组cha中,然后对ekey中每个字节做了相应的操作,若这个字节在偶数位置,则减temp2,若在奇数位置,则加temp2,这里需要注意的是,char类型的范围是-128到127,如果使用python的话需要加上范围限制。基本算法分析结束,首先将apk解压,在里面找到src.jpg这个文件,使用十六进制编辑器打开,把数据复制下来,保存到txt文件中,如下所示
下面试获取flag脚本
#coding:utf-8 ekey = [ord(i) for i in "Tr43Fla92Ch4n93"] cha = [] f = open("src.txt",'r') data = f.read() for i in range(0x400*3): cha.append(int(data[3*i:3*i+2],16)) flag = "" for i in range(len(ekey)): # 注意char类型是-128到127 if cha[ekey[i]] <128: temp2 = cha[ekey[i]] % 10 else: temp2 = (-(cha[ekey[i]]%128))%10 if i%2==1: ekey[i] += temp2 else: ekey[i] -= temp2 for i in ekey: flag += chr(i) print flag
Flag:Qv49CmZB2Df4jB-
Smali
这里提供了一个smali文件,之前做的时候我是专门查了smali的语法来分析,虽然说分析出来了,不过后来发现了一个效率更高的方法。首先打开Smali2JavaUI这个软件,点击file选择处理单个smali文件,直接就能将其转换为java代码,代码如下
/**
* Generated by smali2java 1.0.0.558
* Copyright (C) 2013 Hensence.com
*/
package net.bluelotus.tomorrow.easyandroid;
import android.util.Base64;
import java.io.PrintStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;
import java.security.Key;
import java.security.GeneralSecurityException;
public class Crackme {
private String str2 = "cGhyYWNrICBjdGYgMjAxNg==";
public Crackme() {
GetFlag("sSNnx1UKbYrA1+MOrdtDTA==");
}
private String GetFlag(String p1) {
byte[] "content" = Base64.decode(p1.getBytes(), 0x0);
String "kk" = new String(Base64.decode(str2.getBytes(), 0x0));
System.out.println(decrypt("content", "kk"));
return null;
}
private String decrypt(byte[] p1, String p2) {
String "m" = 0x0;
try {
byte[] "keyStr" = p2.getBytes();
SecretKeySpec "key" = new SecretKeySpec("keyStr", "AES");
Cipher "cipher" = Cipher.getInstance("AES/ECB/NoPadding");
"cipher".init(0x2, "key");
byte[] "result" = "cipher".doFinal(p1);
return "m";
} catch(NoSuchPaddingException "e") {
"e".printStackTrace();
}
return "m";
}
}
这里可以看到首先将str2进行base64解码,然后将其作为key,使用AES的ECB模式去解密,而这个"sSNnx1UKbYrA1+MOrdtDTA=="字符串进行base64解码后就是密文,因此就可以写脚本了
from Crypto.Cipher import AES
import base64,binascii
a = base64.b64decode('sSNnx1UKbYrA1+MOrdtDTA==')
b = base64.b64decode('cGhyYWNrICBjdGYgMjAxNg==')
x = AES.new(b,AES.MODE_ECB)
print x.decrypt(a)
Flag:PCTF{Sm4liRiver}
爬楼梯
这道题比较特别,我们可以采用软件破解的方法来得到flag而不是算法分析。为什么这么说呢,首先我们运行一遍apk,如下所示
第二个按钮时没办法点击的,而每点一次爬一层楼,已爬楼层就会增加1。其实这里不用我们输入数据,说明flag就已经保存在软件中,那么当第二个按钮能被激活的时候,我们就可以得到flag。所以我们的思路是将第二个按钮的标志设置为可点击即可。下面是破解的流程
这个函数第一个参数我们可以猜测出是选择button,第二个参数就是设定标志位。第一个setClickable我们看到是0x1,说明可被点击,我们便将第二个setClickable也改为0x1
保存,然后使用APKToolBox回编译apk,任意命名一个apk,然后就会生成一个新的apk,运行新生成的apk的签名apk(如CFF_100(1)_Signed.apk),点击第二个按钮得到flag