首页
社区
课程
招聘
[原创]攻防世界Mobile通关记录_easyapk_dex_java_jni_so_wp
2022-10-18 17:21 5454

[原创]攻防世界Mobile通关记录_easyapk_dex_java_jni_so_wp

2022-10-18 17:21
5454

前言

业余时间写的,纯兴趣更新!

easy-apk

题目信息

 

下载安装并打开,随便输入字符串点击check按钮,弹框验证失败。

 

分析

将其拖入jeb中查看MainActvity

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
package com.testjava.jack.pingan1;
 
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
    public MainActivity() {
        super();
    }
 
    protected void onCreate(Bundle arg3) {
        super.onCreate(arg3);
        this.setContentView(0x7F04001B);
        this.findViewById(0x7F0B0076).setOnClickListener(new View$OnClickListener() {
            public void onClick(View arg8) {
                if(new Base64New().Base64Encode(MainActivity.this.findViewById(0x7F0B0075).getText().toString().getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=")) {
                    Toast.makeText(MainActivity.this, "验证通过!", 1).show();
                }
                else {
                    Toast.makeText(MainActivity.this, "验证失败!", 1).show();
                }
            }
        });
    }
}

调用了Base64New()类中的Base64Encode(arg)方法,其中参数为我们的输入

1
MainActivity.this.findViewById(0x7F0B0075).getText().toString().getBytes()

如果返回值和"5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="相等的话则验证通过。

 

我们看下Base64New()类

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
package com.testjava.jack.pingan1;
 
public class Base64New {
    private static final char[] Base64ByteToStr = null;
    private static final int RANGE = 0xFF;
    private static byte[] StrToBase64Byte;
 
    static {
        Base64New.Base64ByteToStr = new char[]{'v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/'};
        Base64New.StrToBase64Byte = new byte[0x80];
    }
    public Base64New() {
        super();
    }
    public String Base64Encode(byte[] input) {
        int v7 = 3;
        StringBuilder ret = new StringBuilder();
        int i;
        for(i = 0; i <= input.length - 1; i += 3) {
            byte[] v0 = new byte[4];
            byte v4 = 0;
            int offset;
            for(offset = 0; offset <= 2; ++offset) {
                if(i + offset <= input.length - 1) {
                    v0[offset] = ((byte)((input[i + offset] & 0xFF) >>> offset * 2 + 2 | v4));
                    v4 = ((byte)(((input[i + offset] & 0xFF) << (2 - offset) * 2 + 2 & 0xFF) >>> 2));
                }
                else {
                    v0[offset] = v4;
                    v4 = 0x40;
                }
            }
            v0[v7] = v4;
            for(offset = 0; offset <= v7; ++offset) {
                if(v0[offset] <= 0x3F) {
                    ret.append(Base64New.Base64ByteToStr[v0[offset]]);
                }
                else {
                    ret.append('=');
                }
            }
        }
        return ret.toString();
    }
}

发现这个base64将加密的表给替换成了

1
{'v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/'}

解题

python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64
import string
import binascii
base64_ok="5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="
 
outtab  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 
 
 
table=['v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/']
intab = ""
for i in range(len(table)):
    intab+=table[i]
print(intab)
 
 
teaout=(base64.b64decode(base64_ok.translate(base64_ok.maketrans(intab,outtab))))
print(teaout)#b'05397c42f9b6da593a3644162d36eb01'

验证通过,flag为flag{05397c42f9b6da593a3644162d36eb01}

 

easy-dex

题目信息

 

下载附件,并安装

 

 

打开后只有一个空白界面颜色不断变化

分析

拖入jeb中查看反编译却只有一个类

 

 

apktool对它进行解包

1
2
3
4
5
6
7
8
9
10
11
>apktool d easy-dex.apk
I: Using Apktool 2.6.1 on easy-dex.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: C:\Users\yang\AppData\Local\apktool\framework\1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

但里面却没有dex代文件,奇奇怪怪。

 

 

查看logcat,发现一句可以日志,发现有使用去加载so库,

 

 

同时发现可以log输出

1
Can you shake your phone 100 times in 10 seconds?


难道要在10秒内摇晃100次手机就会显示出flag了吗。

 

打开后黑屏一会时间然后打印出 "Oh yeah~ You Got it~ 99 times to go~"

 

 

虚拟机无法显示,即使真机手速也跟不上。

 

所以这里将so库拖入ida中,搜索该字符串通过交叉引用去定位到android_main代码。

 

解题

看android_main代码,因为这一块从打印"Can you shake your phone 100 times in 10 seconds?"顺序执行到打印""Oh yeah~ You Got it~ 99 times to go~"",中间的代码就不用细看了。

 

 

往下面走将加密的dex数据的size等分为了10份。(不是等分,最后一份比前九份大6字节)

 

每当摇晃到第9 19 29...89时便对其中一份数据进行异或的解密处理。

 

其中

 

第i(0开始)中的数据与i*10+9进行异或 异或的结果存放到了v3

 

最后一份的数据与0x59进行异或 异或的结果存放到了v3中

 

继续往下看

 

当摇晃次数为100的时候再去计算花费时间的,如果时间大于10秒,就把&unk_7004中的数据给复制到v3中,即将加密的dex数据给复原。而如果小于10秒就对解密后的v3进行解压处理。

 

 

另外看一下这里的filename和name是怎么来的,在android_main函数刚开始的时候有一段代码

 

我们将它模拟运行,去得到下filename和name是什么

 

首先是filename,得到"/data/data/com.a.sample.findmydex/files/classes.dex"

1
2
3
4
5
6
7
8
9
10
11
12
filename=[0xc6,0x8d,0x88,0x9d,0x88,0xc6,0x8d,0x88,0x9d,0x88,0xc6,0x8a,0x86,0x84,0xc7,0x88,0xc7,0x9a,0x88,0x84,0x99,0x85,0x8c,0xc7,0x8f,0x80,0x87,0x8d,0x84,0x90,0x8d,0x8c,0x91,0xc6,0x8f,0x80,0x85,0x8c,0x9a,0xc6,0x8a,0x85,0x88,0x9a,0x9a,0x8c,0x9a,0xc7,0x8d,0x8c,0x91,0xe9]
filename[0]=0x2f
#print(filename)
filenameout=[]
filenameout.append(filename[0])
for i in range(1,len(filename)):
    filenameout.append(filename[i]^0xe9)
print(filenameout)
print(len(filenameout))
 
for i in range(len(filenameout)):
    print(chr(filenameout[i]),end="")#/data/data/com.a.sample.findmydex/files/classes.dex

然后是name得到"/data/data/com.a.sample.findmydex/files/odex/"

1
2
3
4
5
6
7
8
9
10
11
12
name=[0xc6,0x8d,0x88,0x9d,0x88,0xc6,0x8d,0x88,0x9d,0x88,0xc6,0x8a,0x86,0x84,0xc7,0x88,0xc7,0x9a,0x88,0x84,0x99,0x85,0x8c,0xc7,0x8f,0x80,0x87,0x8d,0x84,0x90,0x8d,0x8c,0x91,0xc6,0x8f,0x80,0x85,0x8c,0x9a,0xc6,0x86,0x8d,0x8c,0x91,0xc6,0xe9]
name[0]=0x2f
#print(name)
nameout=[]
nameout.append(filename[0])
for i in range(1,len(name)):
    nameout.append(name[i]^0xe9)
print(nameout)
print(len(nameout))
 
for i in range(len(nameout)):
    print(chr(nameout[i]),end="")#/data/data/com.a.sample.findmydex/files/odex/

所以第一步先得到解密后的dex文件

 

在ida将它给dump出来

1
2
3
4
5
6
7
import idaapi
start_address=0x7004
data_length=0x3ca10
data=idaapi.get_bytes(start_address, data_length)
fp = open('dump123', 'wb')
fp.write(data)
fp.close()

然后对其进行解密

 

脚本如下,写的有些潦草.

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
#coding:utf8
import zlib
with open("dump123","rb") as f1:
    #读取dump出的加密字符串
    dump_data=list(f1.read())
    #print(dump_data)
    dump_size=0x3ca10
    #处理每一个字节
 
    print(int(dump_size/10)*1)#24833
    print(int(dump_size/10)*2)#49666
    print(int(dump_size/10)*3)#74499
    print(int(dump_size/10)*4)#99332
    print(int(dump_size/10)*5)#124165
    print(int(dump_size/10)*6)#148998
    print(int(dump_size/10)*7)#173831
    print(int(dump_size/10)*8)#198664
    print(int(dump_size/10)*9)#223497
    print(int(dump_size/10)*10)#248330
    for i in range(dump_size):
        if (0<=i<int(dump_size/10)*1):
            dump_data[i]^=9
        elif (int(dump_size/10)*1<=i<int(dump_size/10)*2):
            dump_data[i]^=19
        elif (int(dump_size/10)*2<=i<int(dump_size/10)*3):
            dump_data[i]^=29
        elif (int(dump_size/10)*3<=i<int(dump_size/10)*4):
            dump_data[i]^=39
        elif (int(dump_size/10)*4<=i<int(dump_size/10)*5):
            dump_data[i]^=49
        elif (int(dump_size/10)*5<=i<int(dump_size/10)*6):
            dump_data[i]^=59
        elif (int(dump_size/10)*6<=i<int(dump_size/10)*7):
            dump_data[i]^=69
        elif (int(dump_size/10)*7<=i<int(dump_size/10)*8):
            dump_data[i]^=79
        elif (int(dump_size/10)*8<=i<int(dump_size/10)*9):
            dump_data[i]^=89
        # elif (int(dump_size/10)*9<=i<int(dump_size/10)*10):
        #     dump_data[i]^=99
        else:
            dump_data[i]^=0x59
#print(dump_data)
dump_decode=bytes(dump_data)
#print(dump_decode)
with open("dump_decode","wb") as f2:
    f2.write(zlib.decompress(dump_decode))

解密后的字节并不是文件格式不能导出后通过zip解压,需要进行zlib.decompress解压才得到dex的文件。把它拖入到jadx中,终于看到了安卓代码啊。

 

 

调用了类a,看下类a

 

 

第22行为关键位置

 

MainActivity.b函数的返回值如何和MainActivity.m相等的话就证明我们的输入的正确的。

 

MainActivity.m为

1
2
3
m = [-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83,
64, -83, -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]

Byte.Min_VALUE为-128

 

MainActivity.b函数的第一个参数是编辑框中获取的内容即我们的输入

 

MainActivity.b函数的第二个参数是MainActivity.getString(2131099683)字符串。

 

然后我们可将apk拖入jadx在resources.arsc中进行搜索2131099683

 

 

发现它为"two_fish",即id

 

然后再去strings.xml中查找two_fish字符串的内容为"I have a male fish and a female fish."

 

 

网上谷歌下发现是个Twofish对称加密算法。那就很显而易见了,我们的输入作为明文,加密算法是Twofish对称加密算法,它的key是"I have a male fish and a female fish.",密文是MainActivity.m对应的字符串。找个在线网站解密就好。

 

在线Twofish加密解密、Twofish在线加密解密、Twofish encryption and decryption--查错网 (chacuo.net)

 

因为看了下two_fish的密文输出为base64,所以先将MainActivity.m对应的字符串给改成base64格式。

 

exp如下

1
2
3
4
5
6
7
8
9
10
#coding:utf8
 
import base64
m = [-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83,
64, -83, -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]
en_data = []
for i in m:
    en_data.append(i&0xFF)
print(base64.b64encode(bytes(en_data)))#b'iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N'

然后在线解密即可,成功拿到flag.

1
qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}

 

这题如果不知道这个Twofish是个算法的话,估计但考静态分析是不够的的。有个思路把dex给本来的apk解包后的目录给合成一个apk,然后通过动调的方式去分析,嗯,突然觉得jeb动调也不错,回头更一下。

easyjava

题目信息

 

下载安装,随便输入字符串每点击check按钮,弹出"You are wrong! Bye~",然后程序退出。

 

 

将其拖入jeb中反编译

 

分析

在if中,调用了MainActivity类中的a方法,参数为我们的输入。如果返回值为true,就证明我们的输入是正确的。

 

看下a方法,发现仅调用了b函数

1
2
3
static Boolean a(String arg1) {
    return MainActivity.b(arg1);
}

再看下b函数,首先判断输入字符串首尾是否为"flag{"及"}",重要部分在else中

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
private static Boolean b(String input_str) {
    Boolean ret;
    int v0 = 0;
    if(!input_str.startsWith("flag{")) {
        ret = Boolean.valueOf(false);
    }
    else if(!input_str.endsWith("}")) {
        ret = Boolean.valueOf(false);
    }
    else {
        String input = input_str.substring(5, input_str.length() - 1);//取输入字符串flag{}大括号中间的部分
        b v4 = new b(Integer.valueOf(2));                //实例化类b 为v4对象
        a v5 = new a(Integer.valueOf(3));                //实例化类a 为v5对象
        StringBuilder v3 = new StringBuilder();         //空字符串 v3
        int i = 0;
        while(v0 < input.length()) {
            v3.append(MainActivity.a(input.charAt(v0) + "", v4, v5));//调用MainActivity.a方法(带参的)
                                                                    //参数1 为输入的每个字符
                                                                    //参数2 为对象v4
                                                                    //参数3 为对象v5
                                                                    //返回值 添加到v3字符串中
 
            Integer v6 = Integer.valueOf(v4.b().intValue() / 25);
            if(v6.intValue() > i && v6.intValue() >= 1) {
                ++i;
            }
            ++v0;
        }
        ret = Boolean.valueOf(v3.toString().equals("wigwrkaugala"));//v3需要和"wigwrkaugala"相等,返回才为true
    }
    return ret;
}

看下这两行

1
2
b v4 = new b(Integer.valueOf(2));                //实例化类b 为v4对象
a v5 = new a(Integer.valueOf(3));                //实例化类a 为v5对象

看b的构造方法,相当于传入的参数作为偏移下标,然后将c整型数组给进行倒置赋值给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
public class b {
    public static ArrayList a;
    static String b;
    Integer[] c;
    static Integer d;
 
    static {
        b.a = new ArrayList();
        b.b = "abcdefghijklmnopqrstuvwxyz";
        b.d = Integer.valueOf(0);
    }
 
    public b(Integer arg9) {     //即调用了这个构造方法,参数传入的是整形2
        super();
        this.c = new Integer[]{Integer.valueOf(8), Integer.valueOf(25), Integer.valueOf(17), Integer.valueOf(23), Integer.valueOf(7), Integer.valueOf(22), Integer.valueOf(1), Integer.valueOf(16), Integer.valueOf(6), Integer.valueOf(9), Integer.valueOf(21), Integer.valueOf(0), Integer.valueOf(15), Integer.valueOf(5), Integer.valueOf(10), Integer.valueOf(18), Integer.valueOf(2), Integer.valueOf(24), Integer.valueOf(4), Integer.valueOf(11), Integer.valueOf(3), Integer.valueOf(14), Integer.valueOf(19), Integer.valueOf(12), Integer.valueOf(20), Integer.valueOf(13)};//对整形数组c进行赋值
        int v0;
        for(v0 = arg9.intValue(); v0 < this.c.length; ++v0) {
            b.a.add(this.c[v0]);                    //将整形数组c的第2(传入的参数)位到最后一位添加到给a数组列表
        }
 
        for(v0 = 0; v0 < arg9.intValue(); ++v0) {
            b.a.add(this.c[v0]);                 //然后再将整形数组c的第0位到第2(传入的参数)位添加到a数组列表
        }
    }

即实例化后的对象v4中的变量内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    public static ArrayList a;
    static String b;
    Integer[] c;
    static Integer d;
 
#v4.c
c=[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]
print(len(c))
#v4.a
a=[]
for i in range(2,len(c)):
      a.append(c[i])
for i in range(0,2):
    a.append(c[i])
print(a)#[17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]

看类a的构造方法,和类b的构造方法的是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class a {
    public static ArrayList a;
    static String b;
    Integer[] c;
    static Integer d;
 
    static {
        a.a = new ArrayList();
        a.b = "abcdefghijklmnopqrstuvwxyz";
        a.d = Integer.valueOf(0);
    }
 
    public a(Integer arg8) {//3
        super();
        this.c = new Integer[]{Integer.valueOf(7), Integer.valueOf(14), Integer.valueOf(16), Integer.valueOf(21), Integer.valueOf(4), Integer.valueOf(24), Integer.valueOf(25), Integer.valueOf(20), Integer.valueOf(5), Integer.valueOf(15), Integer.valueOf(9), Integer.valueOf(17), Integer.valueOf(6), Integer.valueOf(13), Integer.valueOf(3), Integer.valueOf(18), Integer.valueOf(12), Integer.valueOf(10), Integer.valueOf(19), Integer.valueOf(0), Integer.valueOf(22), Integer.valueOf(2), Integer.valueOf(11), Integer.valueOf(23), Integer.valueOf(1), Integer.valueOf(8)};
        int v0;
        for(v0 = arg8.intValue(); v0 < this.c.length; ++v0) {
            a.a.add(this.c[v0]);
        }
 
        for(v0 = 0; v0 < arg8.intValue(); ++v0) {
            a.a.add(this.c[v0]);
        }
    }

即实例化后的对象v5(类a)中的变量内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    public static ArrayList a;
    static String b;
    Integer[] c;
    static Integer d;
 
#v5.c
c=[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]
print(len(c))
#v5.a
a=[]
for i in range(3,len(c)):
      a.append(c[i])
for i in range(0,3):
    a.append(c[i])
print(a)#[21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16]

然后我们回过去看MainActivity类的b方法中的else中有调用MainActivity.a方法,所以看下MainActivity.a方法

1
2
3
4
private static char a(String arg1, b arg2, a arg3) {//    参数1 为输入的每个字符                                                                                                        //参数2 为对象v4(类b)
                                                    //参数3 为对象v5(类a)
    return arg3.a(arg2.a(arg1));
}

然后调用了v5(类a)对象中的a方法,参数为v4(类b)对象中的a方法,

 

v4(类b)对象中的a方法的参数为我们的输入的每一个字符。

 

我们先看v4(类b)对象中的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
b.a = [17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
b.b = "abcdefghijklmnopqrstuvwxyz";  
 
    public Integer a(String arg5) {//参数为输入的每个字符
        int v0 = 0;
        Integer v1 = Integer.valueOf(0);
        if(b.b.contains(arg5.toLowerCase())) {  //判断b字符串是否包含 将参数转成小写
            Integer v2 = Integer.valueOf(b.b.indexOf(arg5)); //v2为   参数在b字符串中首次出现的位置,即下标(b字符串中无重复字符)
            while(v0 < b.a.size() - 1) {
                if(b.a.get(v0) == v2) {
                    v1 = Integer.valueOf(v0);            //如果a数组列表中的第某个下标对应的内容和v2相同    v1为的值就取第某个下标
                }
                ++v0;
            }
        }
        else {
            if(arg5.contains(" ")) {
                v1 = Integer.valueOf(-10);
                goto label_24;
            }
            v1 = Integer.valueOf(-1);
        }
    label_24:
        b.a();                        //返回之前调用了a方法
        return v1;                    //返回此下标
    }

再看下a方法是什么

1
2
3
4
5
6
7
8
9
10
11
12
b.a = [17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
b.b = "abcdefghijklmnopqrstuvwxyz";  
b.d = 0;
 
    public static void a() {
        int v0 = b.a.get(0).intValue();//v0是17
        b.a.remove(0); //b.a = [23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
        b.a.add(Integer.valueOf(v0));//b.a = [23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 2517]
        b.b = b.b + "" + b.b.charAt(0); //b.b = "abcdefghijklmnopqrstuvwxyza"
        b.b = b.b.substring(1, 27);     //b.b = "bcdefghijklmnopqrstuvwxyza"
        b.d = Integer.valueOf(b.d.intValue() + 1);//1
    }

相等于每处理一次,v4(类b)对象中的变量都会发生一次变化。

 

接着调用了v5(类a)中的a方法

 

返回的v1作为参数。

 

然后看下v5(类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
a.a = [21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16]
a.b = "abcdefghijklmnopqrstuvwxyz";
static {
        a.a = new ArrayList();
        a.b = "abcdefghijklmnopqrstuvwxyz";
        a.d = Integer.valueOf(0);
    }
    public char a(Integer arg5) {
        char v0_1;
        int v0 = 0;
        Integer v1 = Integer.valueOf(0);
        if(arg5.intValue() == -10) {
            a.a();
            v0_1 = " ".charAt(0);
        }
        else {
            while(v0 < a.a.size() - 1) {
                if(a.a.get(v0) == arg5) {  // 判断a中的a数组列表第几位和传进来的参数(输入的每个字符)相等
                    v1 = Integer.valueOf(v0);  // 如果相等就获得下表v1
                }
                ++v0;
            }
            a.a();
            v0_1 = a.b.charAt(v1.intValue());  // 返回值为a类b字符串的下表对应的字符
        }
        return v0_1;
    }

在返回之前调用了a.a方法

 

看下a.a

1
2
3
4
5
6
7
8
9
10
11
12
    a.b = "abcdefghijklmnopqrstuvwxyz";
    a.d = 0;
 
public static void a() {
    a.d = Integer.valueOf(a.d.intValue() + 1);//每次加1
    if(a.d.intValue() == 25) {                //增加到25才会对a.a数组列表进行一个倒置
        int v0 = a.a.get(0).intValue();
        a.a.remove(0);
        a.a.add(Integer.valueOf(v0));
        a.d = Integer.valueOf(0);
    }
}

当输入的字符串的每一个字符都经过上面的运算后的结果如果和"wigwrkaugala"就可以。

 

逆向去分析下

 

"wigwrkaugala"的长度为12

 

所以每次调用v5(a类).a方法后,其中只会增加v5(a类).d的值,但不会倒置v5(a类).b

 

所以从wigwrkaugala开始逆向分析。

 

第一个字符"w"为a类b字符串的下表对应的字符,而a类b字符串是固定的,所以可反推出"w"在a类b字符串中的下标。有了下标就可以知道v5(类a)中的a方法的参数是什么了。得到的v5(类a)中的a方法的参数其实就是v4(类b)中的a方法的返回值,这个返回值是v4(类b)中的a数组列表中的下标 (根据b.a.get(v0) == v2),有了下标就可以得到v2的值,再根据v2 = Integer.valueOf(b.b.indexOf(arg5),得知v2其实是输入的字符在v4(类b)中的b字符串的下标,而v4(类b)中的b字符又是知道的,所以即可得到输入的单个字符。

解题

exp如下

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
#v4.c
c=[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]
print(len(c))
#v4.a
a=[]
for i in range(2,len(c)):
      a.append(c[i])
for i in range(0,2):
    a.append(c[i])
print(a)#[17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
 
 
 
#v5.c
c=[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]
print(len(c))
#v5.a
a=[]
for i in range(3,len(c)):
      a.append(c[i])
for i in range(0,3):
    a.append(c[i])
print(a)#[21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16]
 
 
 
#exp
daan = "wigwrkaugala"
a_b = "abcdefghijklmnopqrstuvwxyz"
#获取daan在a_b的下标
index=[]
for i in range(len(daan)):
    temp=a_b.find(daan[i])
    #print(temp)
    index.append(temp)
print("下标为:",end="")
print(index)
#传进来的参数
a_a=[21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16]
can=[]
for i in range(len(index)):
    can.append(a_a[index[i]])
print("参数为:",end="")
print(can)
 
#这里的参数can其实就是v4(类b)对象中的a方法的返回值。
#根据它再去推v2        v2为输入字符在b字符串中首次出现的位置,即下标(b字符串中无重复字符)
 
flag=""
can=[8, 17, 15, 8, 22, 13, 21, 23, 15, 21, 3, 21]
b_a = [17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
b_b = "abcdefghijklmnopqrstuvwxyz"
 
#获取下标
offset=[]
for i in range(len(can)):
    offset.append(b_a[can[i]])
print(offset)
#获取flag
flag+=b_b[offset[0]]
print(flag)
#懒死了,有更好的解题脚本,不想动了。复制代码再来次。把b_a b_b can的值略改下得了。
 
can=[17, 15, 8, 22, 13, 21, 23, 15, 21, 3, 21]#每次删一个
b_a = [23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25, 17]#首移到尾
b_b = "bcdefghijklmnopqrstuvwxyza"#首移到尾
 
#获取下标
offset=[]
for i in range(len(can)):
    offset.append(b_a[can[i]])
print(offset)
#获取flag
flag+=b_b[offset[0]]
print(flag)
 
 
can=[15, 8, 22, 13, 21, 23, 15, 21, 3, 21]#每次删一个
b_a = [7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25, 17,23]#首移到尾
b_b = "cdefghijklmnopqrstuvwxyzab"#首移到尾
 
#获取下标
offset=[]
for i in range(len(can)):
    offset.append(b_a[can[i]])
print(offset)
#获取flag
flag+=b_b[offset[0]]
print(flag)
 
can=[8, 22, 13, 21, 23, 15, 21, 3, 21]#每次删一个
b_a = [22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25, 17,23,7]#首移到尾
b_b = "defghijklmnopqrstuvwxyzabc"#首移到尾
 
#获取下标
offset=[]
for i in range(len(can)):
    offset.append(b_a[can[i]])
print(offset)
#获取flag
flag+=b_b[offset[0]]
print(flag)
 
 
can=[22, 13, 21, 23, 15, 21, 3, 21]#每次删一个
b_a = [1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25, 17,23,7,22]#首移到尾
b_b = "efghijklmnopqrstuvwxyzabcd"#首移到尾
 
#获取下标
offset=[]
for i in range(len(can)):
    offset.append(b_a[can[i]])
print(offset)
#获取flag
flag+=b_b[offset[0]]
print(flag)#venividivkcr     flag{venividivkcr}

 

这题有一说一写的太乱了,如果有人要复现建议的话参考下其它大佬的。

 

这里随便贴几篇吧。

 

(32条消息) 攻防世界easyJava(re Moble)_CTF小白的博客-CSDN博客

 

(32条消息) 攻防世界-easyjava-WriteupSkYe231的博客-CSDN博客

 

攻防世界-Mobile-easyjava-最详细分析 - 灰信网(软件开发博客聚合) (freesion.com)

easyjni

题目信息

 

下载安装运行,看起来和上题差不多

 

分析

将其拖入jeb中查看java代码

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
package com.a.easyjni;
 
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.c;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Toast;
 
public class MainActivity extends c { //加载native的so库
    static {
        System.loadLibrary("native");
    }
    public MainActivity() {
        super();
    }
    static boolean a(MainActivity arg1, String arg2) {
        return arg1.a(arg2);
    }
    private boolean a(String arg3) {
        boolean v0_1;
        try {
            v0_1 = this.ncheck(new a().a(arg3.getBytes()));
        }
        catch(Exception v0) {
            v0_1 = false;
        }
        return v0_1;
    }
    private native boolean ncheck(String arg1) {
    }
    protected void onCreate(Bundle arg3) {
        super.onCreate(arg3);
        this.setContentView(0x7F04001B);
        this.findViewById(0x7F0B0076).setOnClickListener(new View$OnClickListener(((Context)this)) {
            public void onClick(View arg4) {
                if(MainActivity.a(this.b, this.a.findViewById(0x7F0B0075).getText().toString())) {
                    Toast.makeText(this.a, "You are right!", 1).show();
                }
                else {
                    Toast.makeText(this.a, "You are wrong! Bye~", 1).show();
                }
            }
        });
    }
}

当点击check时调用MainActivity.a方法,其中参数1为this.b,参数2为输入字符串,看下MainActivity.a方法

1
2
3
4
5
6
7
8
9
10
11
private boolean a(String arg3) {
    boolean v0_1;
    try {
        v0_1 = this.ncheck(new a().a(arg3.getBytes()));
    }
    catch(Exception v0) {
        v0_1 = false;
    }
 
    return v0_1;
}

发现实例化了类a对象,并调用了a.a方法,参数为输入的字符串。,返回的结果继续作为this.ncheck的方法。

 

看下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
package com.a.easyjni;
 
public class a {
    private static final char[] a;
 
    static {
        a.a = new char[]{'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'};
    }
 
    public a() {
        super();
    }
    public String a(byte[] arg10) {
        int v8 = 3;
        StringBuilder v4 = new StringBuilder();
        int v0;
        for(v0 = 0; v0 <= arg10.length - 1; v0 += 3) {
            byte[] v5 = new byte[4];
            int v3 = 0;
            byte v2 = 0;
            while(v3 <= 2) {
                if(v0 + v3 <= arg10.length - 1) {
                    v5[v3] = ((byte)(v2 | (arg10[v0 + v3] & 0xFF) >>> v3 * 2 + 2));
                    v2 = ((byte)(((arg10[v0 + v3] & 0xFF) << (2 - v3) * 2 + 2 & 0xFF) >>> 2));
                }
                else {
                    v5[v3] = v2;
                    v2 = 0x40;
                }
                ++v3;
            }
            v5[v8] = v2;
            int v2_1;
            for(v2_1 = 0; v2_1 <= v8; ++v2_1) {
                if(v5[v2_1] <= 0x3F) {
                    v4.append(a.a[v5[v2_1]]);
                }
                else {
                    v4.append('=');
                }
            }
        }
        return v4.toString();
    }
}

很容易看出base64编码,但是table表是有发生变化的。加密后的结果作为返回值。

 

然后看this.ncheck代码

1
2
private native boolean ncheck(String arg1) {
}

发现是它的代码实现在so库中,首先使用apktool对它进行解包,获得so库,然后再so库导入ida中进行分析

 

找到Java_com_a_easyjni_MainActivity_ncheck函数

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
signed int __fastcall Java_com_a_easyjni_MainActivity_ncheck(_JNIEnv *env, int a2, int a3)
{
  int v3; // r8
  _JNIEnv *v4; // r5
  int v5; // r8
  const char *base64_str; // r6
  int i; // r0
  char *v8; // r2
  char v9; // r1
  int j; // r0
  bool v11; // nf
  unsigned __int8 v12; // vf
  int v13; // r1
  signed int result; // r0
  char s1[32]; // [sp+3h] [bp-35h]
  char temp; // [sp+23h] [bp-15h]
  int v17; // [sp+28h] [bp-10h]
 
  v17 = v3;
  v4 = env;
  v5 = a3;
  base64_str = (const char *)((int (__fastcall *)(_JNIEnv *, int, _DWORD))env->functions->GetStringUTFChars)(env, a3, 0);
  if ( strlen(base64_str) == 32 )
  {
    i = 0;
    do
    {
      v8 = &s1[i];
      s1[i] = base64_str[i + 16];               // s1从下标16开始取   取16
      v9 = base64_str[i++];
      v8[16] = v9;                              // v8从下标0开始取  取16
    }
    while ( i != 16 );                        //16
    ((void (__fastcall *)(_JNIEnv *, int, const char *))v4->functions->ReleaseStringUTFChars)(v4, v5, base64_str);
    j = 0;
    do
    {                                           // 正数减正数不会溢出 这里v12恒为0
      v12 = __OFSUB__(j, 30);                   // 当j大于或等于30 v11为也为0
      v11 = j - 30 < 0;
      temp = s1[j];                             // 从输入的后16位进行取值
      s1[j] = s1[j + 1];                        // 位置互换
      s1[j + 1] = temp;
      j += 2;                                   // 每轮+2
    }
    while ( v11 ^ v12 );                        // 即当j>=30时,v11^v12才不满足条件,结束循环
    v13 = memcmp(s1, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u);// 最后和目标字符串作比较
    result = 0;
    if ( !v13 )
      result = 1;
  }
  else
  {
    ((void (__fastcall *)(_JNIEnv *, int, const char *))v4->functions->ReleaseStringUTFChars)(v4, v5, base64_str);
    result = 0;
  }
  return result;
}

其实这里的算法很简单,首先将将传进来的base64编码后的字符串进行前16个字节数据和后面16字节数据进行颠倒存放到s1数组中。

 

然后再将s1数组的前32位的每2位进行互换位置,

 

最后的结果和 "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"需要是一致的。

 

所以逆过去即可。

解题

exp

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
#coding:utf8
jieguo="MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"
str1=list(jieguo)
#print(str1)#['M', 'b', 'T', '3', 's', 'Q', 'g', 'X', '0', '3', '9', 'i', '3', 'g', '=', '=', 'A', 'Q', 'O', 'o', 'M', 'Q', 'F', 'P', 's', 'k', 'B', '1', 'B', 's', 'c', '7']
str2=""
for i in range(0,32,2):
    str1[i],str1[i+1]=str1[i+1],str1[i]
#print(str1)#['b', 'M', '3', 'T', 'Q', 's', 'X', 'g', '3', '0', 'i', '9', 'g', '3', '=', '=', 'Q', 'A', 'o', 'O', 'Q', 'M', 'P', 'F', 'k', 's', '1', 'B', 's', 'B', 'c', '7']
 
str1[:16], str1[16:] = str1[16:], str1[:16]
print(str1)#['Q', 'A', 'o', 'O', 'Q', 'M', 'P', 'F', 'k', 's', '1', 'B', 's', 'B', 'c', '7', 'b', 'M', '3', 'T', 'Q', 's', 'X', 'g', '3', '0', 'i', '9', 'g', '3', '=', '=']
str2=""
for i in range(len(str1)):
    str2+=str1[i]
print(str2)
 
 
 
import base64
import string
import binascii
base64_ok="QAoOQMPFks1BsB7cbM3TQsXg30i9g3=="
 
outtab  = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 
 
table=['i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N']
intab = ""
for i in range(len(table)):
    intab+=table[i]
print(intab)#flag{just_ANot#er_@p3}
 
teaout=(base64.b64decode(base64_ok.translate(base64_ok.maketrans(intab,outtab))))
print(teaout)#b'flag{just_ANot#er_@p3}'

 

XCTF-easyjni - S1mba - 博客园 (cnblogs.com)

 

(32条消息) IDA OFSUB 测试_hambaga的博客-CSDN博客

 

IDA头文件摘录_Em0s_Er1t的博客-程序员宅基地_ida 头文件 - 程序员宅基地 (cxyzjd.com)

easy-so

题目信息

 

下载安装运行,随便输入,点击check,弹框验证失败。

 

分析

将其拖入jeb中,查看MainActivity类,当我们点击check按钮时,会调用类cyberpeace中的CheckString方法了,参数位我们的输入。

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
package com.testjava.jack.pingan2;
 
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
    public MainActivity() {
        super();
    }
 
    protected void onCreate(Bundle arg3) {
        super.onCreate(arg3);
        this.setContentView(0x7F09001B);
        this.findViewById(0x7F070022).setOnClickListener(new View$OnClickListener() {
            public void onClick(View arg6) {
                if(cyberpeace.CheckString(MainActivity.this.findViewById(0x7F070031).getText().toString()) == 1) {
                    Toast.makeText(MainActivity.this, "验证通过!", 1).show();
                }
                else {
                    Toast.makeText(MainActivity.this, "验证失败!", 1).show();
                }
            }
        });
    }
}

额,逻辑好简单。看下类cyberpeace中的CheckString是怎么实现的

1
2
3
4
5
6
7
8
9
10
11
12
package com.testjava.jack.pingan2;
 
public class cyberpeace {
    static {
        System.loadLibrary("cyberpeace");
    }
    public cyberpeace() {
        super();
    }
    public static native int CheckString(String arg0) {
    }
}

发现这个方法是在cyberpeace.so中实现的。

 

解包得到so库,将其拖入ida中,查看Java_com_testjava_jack_pingan2_cyberpeace_CheckString函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
signed int __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(_JNIEnv *env)
{
  signed int v1; // r8
  const char *input; // r9
  size_t len; // r6
  const char *input_str; // r5
  signed int v5; // r1
 
  v1 = 0;
  input = (const char *)((int (*)(void))env->functions->GetStringUTFChars)();
  len = strlen(input);
  input_str = (const char *)malloc(len + 1);
  v5 = 0;
  if ( len != -1 )
    v5 = 1;
  _aeabi_memclr(&input_str[len], v5);
  _aeabi_memcpy(input_str, input, len);
  j_TestDec((int)input_str);
  if ( !strcmp(input_str, "f72c5a36569418a20907b55be5bf95ad") )
    v1 = 1;
  return v1;
}

将输入的字符串经过了j_TestDec函数处理,返回的结果需要和"f72c5a36569418a20907b55be5bf95ad"相等。

 

然后分析下j_TestDec函数

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
size_t __fastcall TestDec(const char *input)
{
  char *v1; // r4
  size_t v2; // r5
  int v3; // r1
  char v4; // r0
  size_t result; // r0
  int j; // r5
  int v7; // r0
  char temp; // r1
  unsigned int v9; // r1
 
  v1 = (char *)input;
  if ( strlen(input) >= 2 )
  {
    v2 = 0;
    do
    {
      v3 = (int)&v1[v2];
      v4 = v1[v2];
      v1[v2] = v1[v2 + 16];
      ++v2;
      *(_BYTE *)(v3 + 16) = v4;
    }
    while ( v2 < strlen(v1) >> 1 );  //将前后16字节数据进行换位置  结果存放到v1中
  }
  result = (unsigned __int8)*v1;
  if ( *v1 )
  {
    *v1 = v1[1];
    v1[1] = result;
    result = strlen(v1);
    if ( result >= 3 )
    {
      j = 0;
      do
      {
        v7 = (int)&v1[j];
        temp = v1[j + 2];
        *(_BYTE *)(v7 + 2) = v1[j + 3];
        *(_BYTE *)(v7 + 3) = temp;
        result = strlen(v1);
        v9 = j + 4;
        j += 2;
      }
      while ( v9 < result ); //将v1中的每2个字符都换下位置
    }
  }
  return result;
}

解题

这题和easyjni一样,还少了一个base64编码。

 

将 "f72c5a36569418a20907b55be5bf95ad"先进行每2位换位,然后再前后的16位的字符进行互换就是flag

 

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding:utf8
jieguo="f72c5a36569418a20907b55be5bf95ad"
str1=list(jieguo)
print(str1)
str2=""
for i in range(0,32,2):
    str1[i],str1[i+1]=str1[i+1],str1[i]
print(str1)
 
str1[:16], str1[16:] = str1[16:], str1[:16]
print(str1)
str2=""
for i in range(len(str1)):
    str2+=str1[i]
print(str2)#90705bb55efb59da7fc2a5636549812a

即flag{90705bb55efb59da7fc2a5636549812a}


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 309
活跃值: (960)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
陌上恋静 2022-10-18 17:23
2
0
本人菜鸡一枚,欢迎大佬们陆续加入到QQ群 801022487 一起交流学习
雪    币: 1058
活跃值: (575)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
TrumpWY 2022-10-18 17:50
3
0
大佬  
游客
登录 | 注册 方可回帖
返回