Easy Mobile of ADWorld

攻防世界刷题:Mobile, Easy部分。

easy-so

so层逆向,简单的字符串变换,先交换前后16个字符,然后每两个字符交换位置。
顺便体验了一下so动态调试。

https://zhuanlan.zhihu.com/p/58468014
https://blog.csdn.net/hbhgyu/article/details/81321923

image-20210923210058516

app1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
this.btn.setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View arg10) {
try {
String inputString = MainActivity.this.text.getText().toString();
PackageInfo pinfo = MainActivity.this.getPackageManager().getPackageInfo("com.example.yaphetshan.tencentgreat", 0x4000);
String versionCode = pinfo.versionName; //"X<cP[?PHNB<P?aj";
int versionName = pinfo.versionCode; //15
int i;
for(i = 0; i < inputString.length() && i < versionCode.length(); ++i) {
if(inputString.charAt(i) != (versionCode.charAt(i) ^ versionName)) {
Toast.makeText(MainActivity.this, "再接再厉,加油~", 1).show();
return;
}
}

if(inputString.length() == versionCode.length()) {
Toast.makeText(MainActivity.this, "恭喜开启闯关之门!", 1).show();
return;
}
}
catch(PackageManager.NameNotFoundException v5) {
}

Toast.makeText(MainActivity.this, "年轻人不要耍小聪明噢", 1).show();
}
});

app2

有点儿莫名其妙的一个题。

com.tencent.testvuln.SecondActivity.onCreate中,检查了username和password,具体是通过native的doRawData()函数基于AES进行校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override  // com.tencent.testvuln.a
protected void onCreate(Bundle arg6) {
super.onCreate(arg6);
this.setContentView(0x7F030001); // layout:activity_main2
Intent v0 = this.getIntent();
String ili_name = v0.getStringExtra("ili"); // name
String lil_pwd = v0.getStringExtra("lil"); // pwd
// Base64(AES(name+pwd, key)) == "VEIzd/V2UPYNdn/bxH3Xig=="
if(Encryto.doRawData(this, ili_name + lil_pwd).equals("VEIzd/V2UPYNdn/bxH3Xig==")) {
v0.setAction("android.test.action.MoniterInstallService");
v0.setClass(this, MoniterInstallService.class);
v0.putExtra("company", "tencent");
v0.putExtra("name", "hacker");
v0.putExtra("age", 18);
this.startActivity(v0);
this.startService(v0);
}

SharedPreferences.Editor v0_1 = this.getSharedPreferences("test", 0).edit();
v0_1.putString("ilil", ili_name);
v0_1.putString("lili", lil_pwd);
v0_1.commit();
}

跟到native层的doRawData(),发现直接给出的AES的加密模式和加密密钥。去http://tool.chacuo.net/cryptaes/解出来得到`username=tencent password=aimage`。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
jstring __fastcall doRawData(JNIEnv *env, jobject thiz, int jclass, int str)
{
const char *String; // r6
const char *v7; // r8
jstring result; // r0
jstring (*v9)(JNIEnv *, const jchar *, jsize); // r6
char *v10; // r5
size_t v11; // r2
char key[24]; // [sp+0h] [bp-28h] BYREF

if ( j_checkSignature((int)env, (int)thiz, jclass) == 1 )// j_checkSignature(env, thiz, jclass)
{
strcpy(key, "thisisatestkey==");
String = (*env)->GetStringUTFChars(env, str, 0);
v7 = (const char *)j_AES_128_ECB_PKCS5Padding_Encrypt(String, key);
(*env)->ReleaseStringUTFChars(env, (jstring)str, String);
result = (*env)->NewStringUTF(env, v7);
}
else
{
v9 = (*env)->NewString;
v10 = UNSIGNATURE[0];
v11 = strlen(UNSIGNATURE[0]); // "UNSIGNATURE"
result = v9(env, (const jchar *)v10, v11);
}
return result;
}

但是这个信息没什么用,之后的代码去开启了一个service监听指定路径下的文件变化,但是监听的回调函数也没进行什么可疑操作,不知道在哪儿去操作或者展示了flag。
扫一下其他class的代码,发现了在FileDataActivity中进行了AES解密操作,解出来得到flag = Cas3_0f_A_CAK3

1
2
3
4
5
6
7
8
9
10
11
public class FileDataActivity extends a {
private TextView c;

@Override // com.tencent.testvuln.a
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(0x7F030002); // layout:activity_main3
this.c = (TextView)this.findViewById(0x7F070000); // id:textView1
this.c.setText(Encryto.decode(this, "9YuQ2dk8CSaCe7DTAmaqAA=="));
}
}

这个activity貌似没有在其他class的代码中被调用过,不知道在哪儿暗示了这个是flag,反正就挺莫名其妙的。

app3

下载得到一个.ab,它是Android的备份文件,用工具打开得到一个apk和两个db文件。
这个apk在我的设备上运行不了,提示缺少一些so文件,在这个仓库里面可以找到多数的so文件,然后补上icu文件依然运行不了,遂作罢。

于是静态分析代码,关键在这个方法里面。主要是根据name=Stranger password=123456生成了数据库的加密密钥,且该数据库通过sqlcipher完成加解密操作。
跟着代码逻辑,可以用在线工具得到数据库密码为ae56f99,然后使用工具sqlcipher-shell解密两个数据库文件,从中可以翻到flag的base64字符串。
也可以直接用sqlitebrowser打开,选择Sqlcipher3默认。

1
2
3
4
5
6
7
8
9
10
11
private void a() {
SQLiteDatabase.loadLibs(this);
this.mySQLhelper = new mySQLutils(this, "Demo.db", null, 1);
ContentValues v0 = new ContentValues();
v0.put("name", "Stranger");
v0.put("password", Integer.valueOf(123456));
a v1 = new a();
String Stra1234 = v1.concat4_4(v0.getAsString("name"), v0.getAsString("password"));
this.a = this.mySQLhelper.getWritableDatabase(v1.SHA1(Stra1234 + v1.md5(Stra1234, v0.getAsString("password"))).substring(0, 7));
this.a.insert("TencentMicrMsg", null, v0);
}

image-20220106150732621

easy-apk

1
2
3
4
5
6
7
8
9
10
11
#换表base64解密脚本,https://www.programminghunter.com/article/2612605879/
#结果要包裹flag{}
import base64
import string

str1 = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q"

string1 = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))

easyjava

检查代码发现是一个类似于转轮机的加密程序,把反编译代码复制下来,添加爆破的代码片段即可。
需要注意的是,我的jeb对一些细节处理不够好,比如下面这个转动密码表的rot1函数。

image-20211010134638847

在汇编代码中,先是把list中的头部元素取出,然后remove(0),接着把取出的头部元素加入的尾部。
但是在反编译的得到的java代码中,却先remove(0)移除了头部元素,然后再把头部元素(相当于第二个元素)复制到末尾,造成逻辑错误,需要交换顺序。
同样的情况还发生在com.a.easyjava.a.a() 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import java.util.ArrayList;

class a {
public static ArrayList integerList;
static String b;
Integer[] c;
static Integer cnt;

public a(Integer rotIndex) {
a.integerList = new ArrayList();
a.b = "abcdefghijklmnopqrstuvwxyz";
a.cnt = (int)0;

this.c = new Integer[]{7, 14, 16, 21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8};
int v0;
for(v0 = (int)rotIndex; v0 < this.c.length; ++v0) {
a.integerList.add(this.c[v0]);
}

int v0_1;
for(v0_1 = 0; v0_1 < ((int)rotIndex); ++v0_1) {
a.integerList.add(this.c[v0_1]);
}
}

public static void a() {
a.cnt = (int)(((int)a.cnt) + 1);
if(((int)a.cnt) == 25) {
a.integerList.add(Integer.valueOf(((Integer)a.integerList.get(0)).intValue()));
a.integerList.remove(0);
a.cnt = (int)0;
}
}

public char getCharByNumIndex(Integer arg5) {
int v0 = 0;
Integer v1 = (int)0;
if(((int)arg5) == -10) {
a.a();
return " ".charAt(0);
}

while(v0 < a.integerList.size() - 1) {
if(a.integerList.get(v0) == arg5) {
v1 = (int)v0;
}

++v0;
}

a.a();
return a.b.charAt(v1.intValue());
}
}

class b {
public static ArrayList integerList;
static String chars;
Integer[] c;
static Integer rotateCnt;

public b(Integer rotIndex) {

b.integerList = new ArrayList();
b.chars = "abcdefghijklmnopqrstuvwxyz";
b.rotateCnt = (int)0;
this.c = new Integer[]{8, 25, 17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13};

int v0;
for(v0 = (int)rotIndex; v0 < this.c.length; ++v0) {
b.integerList.add(this.c[v0]);
}

int v0_1;
for(v0_1 = 0; v0_1 < ((int)rotIndex); ++v0_1) {
b.integerList.add(this.c[v0_1]);
}
}

public static void rot1() {
b.integerList.add(Integer.valueOf(((Integer)b.integerList.get(0)).intValue()));
b.integerList.remove(0);
b.chars = b.chars + "" + b.chars.charAt(0);
b.chars = b.chars.substring(1, 27);
b.rotateCnt = (int)(((int)b.rotateCnt) + 1);
}

public Integer indexOf(String arg5) {
int ind = 0;
Integer v1 = (int)0;
if(b.chars.contains(arg5.toLowerCase())) {
while(ind < b.integerList.size() - 1) {
if((int)b.integerList.get(ind) == ((int)b.chars.indexOf(arg5))) {
v1 = (int)ind;
}

++ind;
}
}
else {
v1 = arg5.contains(" ") ? ((int)-10) : ((int)-1);
}

b.rot1();
return v1;
}

public Integer b() {
return b.rotateCnt;
}
}

public class adworldeasyjava{
private static char encode(String arg1, b arg2, a arg3) {
char ret= arg3.getCharByNumIndex(arg2.indexOf(arg1));
System.out.println("encode:" + arg1 + "|" + ret);
return ret;
}


public static Boolean adworldeasyjava(String str, Integer pos) {
System.out.println("adworldeasyjava: " + str +"|"+ pos);
String arg8 = str;
String v2 = arg8;
b v4 = new b(((int)2));
a v5 = new a(((int)3));

StringBuilder v3 = new StringBuilder();
int v1 = 0;
int ind = 0;
while(ind < v2.length()) {
v3.append(encode(v2.charAt(ind) + "", v4, v5));
if(((int)(((int)(((int)v4.b()) / 25)))) > v1 && ((int)(((int)(((int)v4.b()) / 25)))) >= 1) {
++v1;
}

++ind;
}
System.out.println("v3:" + v3.toString());

if(v3.toString().equals("wigwrkaugala")){
System.out.println("final answer: " + str);
}

if(v3.charAt(pos) == ("wigwrkaugala").charAt(pos)){
System.out.println("get one");
return true;
}

return false;
// return Boolean.valueOf(v3.toString().equals("wigwrkaugala"));
}

static String chars = "abcdefghijklmnopqrstuvwxyz";

public static void main(String[] args){
String ans = "";
for( int j=0;j<12;j++){
for(int i=25;i>=0;i--){
if(adworldeasyjava( ans + chars.charAt(i), ans.length()) ){
ans = ans + chars.charAt(i);
break;
}
}
}

adworldeasyjava("venividivkcr", 0);
}
}

easyjni

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
import string

a='MbT3sQgX039i3g==AQOoMQFPskB1Bsc'
s=a[16:32] + a[0:16]
for ind in range(0, len(s), 2):
print(s[ind+1] + s[ind], end='')
#QAoOQMPFks1BsB7cbM3TQsXg30i9g3==

string1 = "i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
str1 = 'QAoOQMPFks1BsB7cbM3TQsXg30i9g3=='

print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))
# b'flag{just_ANot#er_@p3}'

RememberOther

无趣的脑洞题:
apk里面藏了个YOU_KNOW_,然后文档里提示本题只是强行沾边了Android,说明出题人不懂Android,而做题人是懂Android的,所以flag是YOU_KNOW_ANDROID
所以出题人看来真的不懂Android。

easy-dex

非常有营养的一道题,解题的思路由一系列的知识点构成,并考察了基础的Android逆向知识。
解题过程参考自neilwu@52pojie

首先打开apk,发现没有任何代码。
检查manifest文件,发现属性hasCode=false,这里需要的知识点为NativeActivity开发,即使用纯C来完成App的开发。

有了这个知识,用ida打开so文件,定位到android_main函数,里面就是关于这个App的主要逻辑代码。
应用要求使用者在十秒之内晃动手机100次,然后解密释放出一个dex文件到指定目录,加载之后来到flag校验的步骤。
具体的流程还是需要我们自己去一点点分析还原。

  1. 文件路径
    dex释放过程中涉及到的两个路径名分别保存在filenamename中,并被抑或加密保护,还原的时候还要每4个字节调整顺序。image-20211017200658963

  2. dex文件解密
    dex文件也通过抑或加密保护,以字节流的方式写在.data中,起始位置是 0x7004,长度是 0x3ca10。

    image-20211017203816974
    整个dex文件流一共分为10段来加解密,解密代码如下:
    image-20211017201220218

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import os
    import zlib

    fr = open("xxx/odex", "rb")
    dd = list(fr.read())
    fr.close()

    L = 0x3ca10
    segLen = L // 10

    for cnt in range(9,99,10):
    rnd = cnt // 10
    for ind in range(segLen * rnd, segLen*rnd + segLen):
    dd[ind] ^= cnt
    if(cnt == 89):
    for ind in range(segLen*rnd+segLen,L):
    dd[ind] ^= 89

    ddd=b''.join(map(lambda x:int.to_bytes(x,1,'little'), dd))

    dddd = zlib.decompress(ddd)

    fw = open("xxx/class.dex", "wb")
    fw.write(dddd)
    fw.close()
  1. dex执行
    程序记录到100次摇晃之后,开始释放dex文件,加载并运行。
    loadDex()runDex()函数中会有从native反射java层执行方法的相关代码。image-20211017201828485

  2. TwoFish
    dex的代码包含了一个flag校验程序,为encode(flag, key)==cipher的模式,加密代码很长很烧脑。

    浏览一下App的字符串信息,有如下发现:
    image-20211017202524656

    这里需要新的知识:TwoFish加密算法,与AES的功能相似(因为也是当初AES海选的参赛选手之一)。
    我们并不需要自己去实现一遍,找个在线工具就好。
    不过还需要确定key和cipher。
    image-20211017203102910
    分析代码,可知cipher=new byte[]{-120, 77, -14, -38, 17, 5, -42, 44, (byte)224, 109, 85, 31, 24, -91, (byte)144, -83, 0x40, -83, (byte)128, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 117, 29, -44, 6, 112, -4, 81, 84, 9, 22, -51, 95, -34, 12, 47, 77}
    key是0x7F060025对应的String,简单的确定方法如下所示,可知key=("I have a male fish and a female fish.").getBytes()

image-20211017203324877

image-20211017203452469

黑客精神

简单扫一下,看起来是注册码校验模式的App,关键的几个操作、检查注册码的函数埋在native层。
从JNI_OnLoad()函数里面可以找到与Java层的initSN() saveSN() work()方法绑定的native层函数。

程序运行时,先要求填入注册码,经过抑或加密之后保存到/sdcard/reg.dat

work()函数中,先调用initSN()检查是否注册,如果已经注册,就修改MainActivity.m=1
initSN()就是检查/sdcard/reg.dat中的数据是不是EoPAoY62@ElRD,如果是的话就设置MainActivity.m=1
initSN结束之后,就去检查MainActivity.m判断是否注册,并返回相应的提示信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int __fastcall work(int a1)
{
int m; // r0
int v3; // r0
void *sendBackStr; // r1
bool v5; // zf

initSN(a1);
m = getValueOfm(a1);
if ( m )
{
v5 = m == 1;
v3 = a1;
if ( v5 )
sendBackStr = &unk_2E6B; // 输入即是flag,格式为xman{……}! (idaapi导出解utf-8编码)
else
sendBackStr = &unk_2E95; // 还不行呢!
}
else
{
v3 = a1;
sendBackStr = &unk_2E5B; // 状态不太对。。
}
return setWorkString(v3, sendBackStr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int __fastcall initSN(JNIEnv *env)
{
FILE *regf; // r0
FILE *regFile; // r4
JNIEnv *v4; // r0
int v5; // r7
void *regStr; // r5
JNIEnv *v8; // r0
int v9; // r1

regf = fopen("/sdcard/reg.dat", "r+");
regFile = regf;
if ( !regf )
{
v4 = env;
return setValue(v4, 0); // setValue(env, v),把Java层的MainActivity.m值改为v
}
fseek(regf, 0, 2);
v5 = ftell(regFile);
regStr = malloc(v5 + 1);
if ( !regStr )
{
fclose(regFile);
v4 = env;
return setValue(v4, 0);
}
fseek(regFile, 0, 0);
fread(regStr, v5, 1u, regFile);
*((_BYTE *)regStr + v5) = 0;
if ( !strcmp((const char *)regStr, "EoPAoY62@ElRD") )
{
v8 = env;
v9 = 1;
}
else
{
v8 = env;
v9 = 0;
}
setValue(v8, v9);
return j_fclose(regFile);
}

返回的字符串提示输入的注册码就是flag,提交的时候包裹上xman{....}就行了。
注册码加密为xor(SignCode, key)=cipher模式,其中cipher=EoPAoY62@ElRD
经过分析,也可以得到key=W3_arE_whO_we_ARE(或者说key=w_a),进一步可以得到flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
int __fastcall saveSN(JNIEnv *env, int thiz, int ptr_sn)
{
FILE *fptr; // r7
_DWORD *Bytess; // r4
const char *key; // r3
int v9; // r0
int v10; // r1
_WORD *v11; // r5
JNIEnv *v12; // r0
int x; // r4
JNIEnv v14; // r3
signed int ind; // r6
const char *sn; // r9
char *sn_ptr; // r5
signed int sn_len; // r10
char v19; // r2
char v20; // r3
_BYTE Bytes[56]; // [sp+0h] [bp-38h] BYREF

fptr = fopen("/sdcard/reg.dat", "w+");
if ( !fptr )
return j___android_log_print(3, "com.gdufs.xman", byte_2DCA);
Bytess = Bytes;
key = "W3_arE_whO_we_ARE";
do
{
v9 = *(_DWORD *)key;
key += 8;
v10 = *((_DWORD *)key - 1);
*Bytess = v9;
Bytess[1] = v10;
v11 = Bytess + 2;
Bytess += 2;
} while ( key != "E" );//这里产生抑或密钥,事实上就是原来的key。
v12 = env;
x = 2016;
*v11 = *(_WORD *)key;
v14 = *env;
ind = 0;
sn = v14->GetStringUTFChars(v12, (jstring)ptr_sn, 0);
sn_ptr = (char *)sn;
sn_len = strlen(sn);
while ( ind < sn_len )
{
if ( ind % 3 == 1 )
{
x = (x + 5) % 16; // x 会进入4,9,1的循环
v19 = Bytes[x + 1]; // Bytes[9+1]
}
else if ( ind % 3 == 2 )
{
x = (x + 7) % 15;
v19 = Bytes[x + 2]; // Bytes[1+2]
}
else
{
x = (x + 3) % 13;
v19 = Bytes[x + 3]; // Bytes[4+3]
}
v20 = *sn_ptr;
++ind;
*sn_ptr++ = v20 ^ v19;//抑或加密
}
fputs(sn, fptr);//写入文件
return j_fclose(fptr);
}

你是谁

首先,构造一个心形图案,约束条件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean check() {
return this.matrix[1][1].getStatus() == 1 && this.matrix[1][2].getStatus() == 1
&& this.matrix[1][7].getStatus() == 1 && this.matrix[1][8].getStatus() == 1
&& this.matrix[2][0].getStatus() == 1 && this.matrix[2][3].getStatus() == 1
&& this.matrix[2][6].getStatus() == 1 && this.matrix[2][9].getStatus() == 1
&& this.matrix[3][0].getStatus() == 1 && this.matrix[3][4].getStatus() == 1
&& this.matrix[3][5].getStatus() == 1 && this.matrix[3][9].getStatus() == 1
&& this.matrix[4][0].getStatus() == 1 && this.matrix[4][9].getStatus() == 1
&& this.matrix[5][1].getStatus() == 1 && this.matrix[5][8].getStatus() == 1
&& this.matrix[6][2].getStatus() == 1 && this.matrix[6][7].getStatus() == 1
&& this.matrix[7][3].getStatus() == 1 && this.matrix[7][6].getStatus() == 1
&& this.matrix[8][4].getStatus() == 1 && this.matrix[8][5].getStatus() == 1;
}
cap

其实把所有圈都点了也能通过,不过这个题只静态分析就行。

关注xyz.konso.testsrtp.background$4.onResult()这个回调函数:
这里接入了科大讯飞的语音SDK,传入的results会被解析为文本信息然后传入getsna()中。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void onResult(RecognizerResult results, boolean isLast) {
Log.d(background.this.TAG, results.getResultString());
String str = results.getResultString();//解析为文本信息,返回一个JSON对象
try {
background.this.ss = new JSONObject(str).getJSONArray("ws").getJSONObject(0).getJSONArray("cw").getJSONObject(0).getString("w");
}
catch(Exception e) {
Log.d(background.this.TAG, "catch Excepetion");
}

background.this.getsna(background.this.ss);
Log.d(background.this.TAG, background.this.ss);
}

getsna()中,逻辑比较简单,输入的flag参数去utf-8解码出来是我是傻逼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void getsna(String flag) {
if(flag.length() != 4) {
return;
}

int[] as = new int[flag.length()];
int i;
for(i = 0; i < flag.length(); ++i) {
as[i] = flag.charAt(i) & 0xFFFF;// string to int
}

int j;
for(j = 0; j < 4; ++j) {//排序
int k;
for(k = j + 1; k < 4; ++k) {
if(as[j] > as[k]) {
int temp = as[j];
as[j] = as[k];
as[k] = temp;
}
}
}
// ‘傻’ ‘我’ ‘是’ ‘逼’
if(as[0] == 0x50BB && as[1] == 25105 && as[2] == 0x662F && as[3] == 0x903C) {
Toast.makeText(this.getContext(), "You get the sorted flag:20667 25105 26159 36924", 0).show();
return;
}

Toast.makeText(this.getContext(), "wrong input", 0).show();
}

但是提交的flag形式是啥还是不知道。
makeText提示sorted flag=20667 25105 26159 36924,那么original flag=25105 26159 20667 36924,且需要保留空格。
最后这一步我是真没想到,看来我真是傻逼了。: (

Welcome to my other publishing channels