• 攻防世界安卓逆向练习


    一.easy-so

    1. jadx分析程序逻辑

    可以看到关键在于cyberpeace.CheckString()函数
    在这里插入图片描述
    双击跟进之后可以发现是native层函数
    在这里插入图片描述

    2. ida查看so文件

    程序逻辑:
    1. 将字符串保存到新的空间buffer中
    2. 第一个判断是将buffer的前16个字符和后16个字符进行交换
    3. 第二个判断是将buffer的2个相邻的字符互换位置

    _BOOL8 __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(__int64 a1, __int64 a2, __int64 a3)
    {
      const char *str; // r14
      size_t len; // rax
      int len2; // r15d
      unsigned __int64 v6; // r12
      char *newMem; // rax
      char *buffer; // r13
      bool v9; // cc
      size_t v10; // r12
      size_t i; // rbx
      char tmp; // al
      char tmp1; // al
      size_t j; // rbx
      char tmp2; // al
    
      str = (*(*a1 + 1352LL))(a1, a3, 0LL);
      len = strlen(str);
      len2 = len;
      v6 = ((len << 32) + 0x100000000LL) >> 32;
      newMem = malloc(v6);
      buffer = newMem;
      v9 = v6 <= len2;
      v10 = v6 - len2;
      if ( v9 )
        v10 = 0LL;
      memset(&newMem[len2], 0, v10);
      memcpy(buffer, str, len2);
      if ( strlen(buffer) >= 2 )
      {
        i = 0LL;
        do
        {
          tmp = buffer[i];
          buffer[i] = buffer[i + 16];
          buffer[i++ + 16] = tmp;
        }
        while ( strlen(buffer) >> 1 > i );
        //32>>1 = 16 也就是说i<16时执行循环
      }
      tmp1 = *buffer;
      if ( *buffer )
      {
        *buffer = buffer[1];
        buffer[1] = tmp1;
        if ( strlen(buffer) >= 3 )
        {
          j = 2LL;
          do
          {
            tmp2 = buffer[j];
            buffer[j] = buffer[j + 1];
            buffer[j + 1] = tmp2;
            j += 2LL;
          }
          while ( strlen(buffer) > j );
        }
      }
      return strcmp(buffer, "f72c5a36569418a20907b55be5bf95ad") == 0;
    }
    
    • 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

    注意,不同的so文件得到的反汇编结果不一样
    一开始我使用arm64-v8a文件夹中的so文件得到的是这个结果,显然是错误的,这个循环最后会丢失一些数据
    在这里插入图片描述
    后来打开x86_64文件夹的so文件就正常了

    3. 解题脚本:

    #include 
    int main()
    {
        char flag[33] = "f72c5a36569418a20907b55be5bf95ad";
        char tmp;
        for (int i = 0; i < 32; i += 2) // 两两交换
        {
            tmp = flag[i];
            flag[i] = flag[i + 1];
            flag[i + 1] = tmp;
        }
        int i = 0;
        do
        {
            tmp = flag[i];
            flag[i] = flag[i + 16];
            flag[i++ + 16] = tmp;
        } while (i<16);
        printf("flag{%s}",flag);
        //flag{90705bb55efb59da7fc2a5636549812a}
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    二.ezjni

    1. 程序逻辑分析

    可以看到也是获取字符串然后加密
    1. 获取字符串
    2. 进行base64加密(base表有更换)
    3. 调用ncheck这个native函数判断结果
    在这里插入图片描述
    base64encode函数,换表base64加密:
    在这里插入图片描述
    ncheck函数,这里应该和上一题easyso是一样的操作,只是由于反编译错误
    1. 先将前16和后16个字符进行交换
    2. 然后从头遍历,每两个字符一组互换位置
    在这里插入图片描述

    2. 解题脚本:

    先输出ncheck处理前的base串

    #include 
    int main()
    {
        char flag[33] = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7";
        char tmp;
        for (int i = 0; i < 32; i += 2) // 两两交换
        {
            tmp = flag[i];
            flag[i] = flag[i + 1];
            flag[i + 1] = tmp;
        }
        int i = 0;
        do
        {
            tmp = flag[i];
            flag[i] = flag[i + 16];
            flag[i++ + 16] = tmp;
        } while (i<16);
        printf("%s",flag);
        //QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    先输出base64表

    public class test1{
        private static final char[] 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'};
    
        public static void main(String[] args) {
            System.out.println("hello");
            String s="hellk";
            System.out.println(s);
            for (int i=0;i<table.length;i++)
            {
                System.out.print(table[i]);
                //i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后用CyberChef嗦一下
    在这里插入图片描述

    三.easyjava

    1. 主函数逻辑

    这题基本和安卓没什么关系,主要是分析java代码逻辑
    主函数这里调用了一个check方法判断输入的flag
    在这里插入图片描述
    再看看check方法
    先判断头尾flag{}格式,然后获取花括号内的字符
    创建两个类用于后续的一些处理
    然后使用stringBuilder和func函数创建一个新的字符串
    注意func函数每次传进去的是单字符的字符串,所以每次append添加的是单个字符
    在这里插入图片描述
    func函数
    用了两个函数来套娃操作
    在这里插入图片描述

    2. getIndex函数

    先看看index函数(注意这里的函数名和变量名都是根据分析手动修改的)
    这里的函数逻辑已经分析出来了,首先判断字符是否为字母,然后获取该字符在str中的下标
    再用下标在array中查找对应位置,然后将array中的下标返回
    最后使用change函数改变表(change是个循环左移一位的操作)
    在这里插入图片描述

    1. array初始化
      在这里插入图片描述
    2. change函数
      在这里插入图片描述

    3. getChar函数

    这个逻辑就比较简单了,也是表中查值然后记录index,根据index在table表中查找字符
    在这里插入图片描述

    1. list和上一个函数有类似的操作
      在这里插入图片描述

    2. judgeCount,由于字符根本没有25那么长,这个函数纯纯干扰让人多分析一下,没什么实际作用
      在这里插入图片描述

    4.解题脚本

    import java.util.ArrayList;
    public class glass0{
        static String str = "abcdefghijklmnopqrstuvwxyz";
        static ArrayList<Integer> array = new ArrayList<>();
        static void change() {
            int value = array.get(0).intValue(); // 首个元素的int值
            array.remove(0); // 移除
            array.add(Integer.valueOf(value)); // 又加回去?
            str += "" + str.charAt(0);
            str = str.substring(1, 27); // 切割字符串并调整,左移一位的效果
        }
        public static void main(String[] args) {
            char[] flag="wigwrkaugala".toCharArray();
            String table = "abcdefghijklmnopqrstuvwxyz";
            int[] Table={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[] intergerArr = {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};
            ArrayList<Integer> list = new ArrayList<>();
            //初始化list
            for(int i=3;i<Table.length;i++){
                list.add(Table[i]);
            }
            for(int i=0;i<3;i++){
                list.add(Table[i]);
            }
            //初始化array
            for (int i = 2; i < intergerArr.length; i++) {
                array.add(intergerArr[i]); // 截取num~length后半段字符串
            }
            for (int j = 0; j < 2; j++) {
                array.add(intergerArr[j]); // 截取0~num-1前半段字符串
            }
            //开始解密
            for(int i=0;i<flag.length;i++){
                //获取下标值
                int index=table.indexOf(flag[i]);
                //获取num值
                int num=list.get(index);
                //这个num是getIndex函数的返回值
                int value=array.get(num);
                char chr=str.charAt(value);
                flag[i]=chr;
                change();
            }
            System.out.println(flag);//venividivkcr
        }
    }
    
    • 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

    flag{venividivkcr}

    四.APK逆向

    1.程序逻辑分析

    对用户名和输入的SN码进行检测,用户名这里已经给出,所以查看checkSN即可
    在这里插入图片描述
    checkSN
    可以看到sn码有22位,然后是对userName进行md5处理,md5之后的十六进制字符串每隔2位取一个,然后用flag{}包裹住就是需要的sn码
    在这里插入图片描述

    2.解题脚本

    1. 对userNamemd5
    2. 对md5结果每2位取一个
    import hashlib
    text = "Tenshine"
    hash_obj = hashlib.md5()
    hash_obj.update(text.encode())
    encrypted_text = hash_obj.hexdigest()
    for i in range(0,len(encrypted_text),2):
        print(encrypted_text[i],end='')
    #bc72f242a6af3857
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    flag:bc72f242a6af3857

    3.动态调试

    使用jeb动态调试
    下断点后运行程序输入22个字符即可成功断下并看到字符串的值
    在这里插入图片描述
    jadx也可以调试,但是感觉没有jeb那么丝滑
    而且这红色的是变量值,第一眼看过去还以为是报错…
    在这里插入图片描述

    Android2.0

    主要是调用了JNI.getResult这个native函数
    在这里插入图片描述
    Init的功能是将str分为三段,分别存储在三个数组中,然后就是一些异或和比较操作了
    在这里插入图片描述
    解题脚本(注意循环是0-3,不包括第五个字符)

    #include
    int main()
    {
    	unsigned char one[5] = "LN^dl";
    	unsigned char two[5] = " 5-0a";
    	two[3] = 0x16;
    	unsigned char three[5] = "AFBo}";
    	unsigned char flag[16] = { 0 };
    	for (int i = 0; i < 4; i++)
    	{
    		three[i] ^= two[i];
    		two[i] ^= one[i];
    		one[i] = (one[i] ^ 0x80) / 2;
    		flag[i * 3] = one[i];
    		flag[i * 3 + 1] = two[i];
    		flag[i * 3 + 2] = three[i];
    	}
    	flag[12] = one[4];
    	flag[13] = two[4];
    	flag[14] = three[4];
    	printf("%s",flag);
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    flag{sosorryla}

    人民的名义-抓捕赵德汉1-200

    jar文件,用jadx打开分析,
    主函数先加载一个checker对象,然后调用该对象中的checkPassword方法检测输入的密码是否正确
    在这里插入图片描述
    下方给出了loadCheckerObject方法的实现,可以看到是对ClassEnc文件进行AES解密,解密之后将该类作为loader方法的值返回
    注意Java Cipher库默认的AES解密为ECB模式,默认填充方式为PKCS5Padding,被GPT说是CBC模式坑了
    在这里插入图片描述
    解密脚本

    import os
    from Crypto.Cipher import AES
    key=bytes().fromhex("bb27630cf264f8567d185008c10c3f96")
    with open("ClassEnc","rb") as input: 
        data=input.read()
        cipher=AES.new(key,AES.MODE_ECB)
        decode= cipher.decrypt(data)
        
    with open("ClassDec","wb") as out:
        out.write(decode)
    
    input.close()
    out.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    用jadx打开解密后的ClassDec文件
    可以发现CheckPass类实现了CheckInterface接口的checkPassword方法
    在这里插入图片描述
    一个简单的md5,在线解密得到monkey99
    所以flag{monkey99}
    在这里插入图片描述
    很多文章都是直接给出jar包中自带的这个CheckPass方法,但是根据程序逻辑来看并没有调用这里,可能是出题人忘了删这一段代码
    在这里插入图片描述

    参考文章:

    1. JDK安全模块JCE核心Cipher使用详解
    2. Java JCE Cipher 详解
    3. XCTF_MOBILE15_人民的名义-抓捕赵德汉1-200

    app3

    .ab文件,chatgpt是这么解释:

    通常情况下,.ab后缀的文件是Android应用程序备份文件,也称为“应用程序包文件”(.apk文件)的备份文件。
    当您使用Android设备上的备份和重置功能时,系统将应用程序的数据和设置打包成一个.ab文件,以便稍后可以还原到设备上。
    这些备份文件可以保存在本地存储设备或云存储中,以防意外数据丢失或设备更换。
    
    • 1
    • 2
    • 3

    .ab有加密和未加密两种,这里未加密就显示none
    在这里插入图片描述
    下载解包工具:android-backup-extractor
    注意:该工具使用需要java11及以上环境
    使用命令

    java -jar abe.jar unpack 399649a0e89b46309dd5ae78ff96917a.ab app3.tar
    
    • 1

    在这里插入图片描述
    然后就会输出app3.tar压缩包,解压即可在文件夹里面找到apk文件
    在这里插入图片描述
    jadx打开分析逻辑

  • 相关阅读:
    ansible 调研
    Shiro学习(10)Session管理
    第二篇 渲染框架2.x
    推动产业数字化转型,六个方面引领变革
    SRS srs-bench
    希尔排序算法
    机器学习笔记 - 深度学习中跳跃连接的直观解释
    超声波清洗机品牌哪些好用?好评不断的超声波清洗机推荐
    JVS规则引擎决策流:轻松解决规则执行中的潜在问题
    [python]pytest运行报错pytest: error: unrecognized arguments: --reruns
  • 原文地址:https://blog.csdn.net/OrientalGlass/article/details/130913551