吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2714|回复: 24
收起左侧

[Android CTF] 学破解第198天,《攻防世界mobile练习区easyjni》学习

[复制链接]
小菜鸟一枚 发表于 2022-8-10 11:28

前言:

  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-1582287-1-1.html

立帖为证!--------记录学习的点点滴滴

0x1 静态分析

  1.将程序下载下来,发现是个apk文件,那就用jadx工具打开看看,先看看主函数,看oncreate函数,一般这就是启动函数了,可以看到程序逻辑就是调用m1a方法,根据返回值决定成功还是失败。

    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { // from class: com.a.easyjni.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                if (MainActivity.this.m1a(((EditText) ((MainActivity) this).findViewById(R.id.edit)).getText().toString())) {
                    Toast.makeText(this, "You are right!", 1).show();
                } else {
                    Toast.makeText(this, "You are wrong! Bye~", 1).show();
                }
            }
        });
    }

  2.再来看m1a方法,发现是new了一个C0678类对象,然后调用m0a方法得到一个返回值,然后通过ncheck去验证。

    public boolean m1a(String str) {
        try {
            return ncheck(new C0678a().m0a(str.getBytes()));
        } catch (Exception e) {
            return false;
        }
    }

  3. 再去看看C0678a这个类,创建类对象时会初始化一个静态字符串f2481a,再看m0a方法,接收一个前面我们在文本框输入的字符串,然后很像base64编码的过程,左右移位,f2481a就是码表,这里仔细看可以看到for循环对我们的字符串进行移位,每三位一组处理成4个字符,最后不足的末尾补=号,转换后的字符串存在sb中,然后返回这个字符串。

public class C0678a {

    /* renamed from: a */
    private static final char[] f2481a = {'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'};

    /* renamed from: a */
    public String m0a(byte[] bArr) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i <= bArr.length - 1; i += 3) {
            byte[] bArr2 = new byte[4];
            byte b = 0;
            for (int i2 = 0; i2 <= 2; i2++) {
                if (i + i2 <= bArr.length - 1) {
                    bArr2[i2] = (byte) (b | ((bArr[i + i2] & 255) >>> ((i2 * 2) + 2)));
                    b = (byte) ((((bArr[i + i2] & 255) << (((2 - i2) * 2) + 2)) & 255) >>> 2);
                } else {
                    bArr2[i2] = b;
                    b = 64;
                }
            }
            bArr2[3] = b;
            for (int i3 = 0; i3 <= 3; i3++) {
                if (bArr2[i3] <= 63) {
                    sb.append(f2481a[bArr2[i3]]);
                } else {
                    sb.append('=');
                }
            }
        }
        return sb.toString();
    }
}

  4.返回值看明白了,现在去看ncheck方法是怎么验证的,跟过去之后发现是一个native方法,那就只能换IDA继续跟了,找到so文件用IDA打开,搜索java,找到了ncheck函数,有些看不懂这些赋值操作,不要晕,先看返回值,result如果是0那肯定就是失败了,所以result必须是1,然后往回看到memcmp比较函数,也就是说v12必须等于"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"。

  v5 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
  if ( strlen(v5) == 32 )
  {
    for ( i = 0; i != 16; ++i )
    {
      v7 = &v12[i];
      v12[i] = v5[i + 16];
      v8 = v5[i];
      v7[16] = v8;
    }
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    v9 = 0;
    do
    {
      v10 = v9 < 30;
      v13 = v12[v9];
      v12[v9] = v12[v9 + 1];
      v12[v9 + 1] = v13;
      v9 += 2;
    }
    while ( v10 );
    result = memcmp(v12, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u) == 0;
  }
  else
  {
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    result = 0;
  }
  return result;
}

  5.现在看看v12,32位的字符串,现在假设v5就是我们输入的字符串编码后的形式,这段代码就好理解了,分为两个循环,第一个for循环就是将v5的前16位与后16位对调赋值给v12。

https://s1.ax1x.com/2022/08/08/vQOCuD.png

  6.再看第二个do while循环v9每次自增2,把中间三行挑出来单独看,这不就是典型的交换两个变量的值嘛,那这里就是每次循环取v12中的两个值,然后交换,最终和"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"比较,相等,就说明验证通过了。

v13 = v12[v9];
v12[v9] = v12[v9 + 1];
v12[v9 + 1] = v13;

0x2 动态分析

  1.运行一下程序,用JEB动态调试看看,发现报错了,原来是上次把root权限给关了造成的,重新打开即可。

https://s1.ax1x.com/2022/08/08/vQjJXT.png

  2.CTRL+B在jeb中打上断点,将apk拖进jeb,然后在输入123456点check按钮,断了下来,直接运行到函数末尾,看右侧变量,可以知道我输入的123456,经过base64编码得到:"f4G91LqC"。

https://s1.ax1x.com/2022/08/08/vQvMDO.png

  3.在打开IDA调试so文件,回忆一下步骤adb shell,mount -o rw,remount /,重新挂载一下文件系统,adb push android_x86_server /sbin ,chmod +x /sbin/android_x86_server,给它可执行权限,然后输入android_x86_server,运行起来监听23946端口,adb forward tcp:23946 tcp:23946命令,将手机上的23946窗口,转发到我们电脑本地的23946端口。

  4.然后一顿操作下来,懵了,不知道咋调试,本来还想验证下v5是不是我想的那样,这只能算了。

0x3 计算flag

  1.把"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"作为结果反推。

  2.还原so第二个循环do while,每两个变量前后位置交换

  3.还原so第一个循环for,交换前16位和后16位

  4.编写base64解码程序,直接把之前自己实现的c++版搬运过来即可:https://www.52pojie.cn/thread-1212431-1-1.html

  5.得到最终的解密代码:

package ctf;

import java.util.Base64;

public class test01 {

    public static void main(String[] args) {

        char[] mb = { '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' };
        String output = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7";
        StringBuffer flag = new StringBuffer("");
        StringBuffer f_flag = new StringBuffer("");

        int i = 0;

        // 每两个变量交换前后的值
        do {
            flag.append(output.charAt(i + 1));
            flag.append(output.charAt(i));
            i += 2;
        } while (i <= 30);

        System.out.println("flag1:" + flag);

        // 交换前16位和后16位
        for (i = 0; i != 16; ++i) {
            char temp = flag.charAt(i);
            flag.setCharAt(i, flag.charAt(i + 16));
            flag.setCharAt(i + 16, temp);
        }

        System.out.println("flag2:" + flag);

        // base64解码得到flag
        i = 0;// 密文下标

        byte[] b_flag = flag.toString().getBytes();
        while (i < b_flag.length)// 判断是否解密完毕
        {
            int index = 0;// 码表下标
            int temp1 = -1;// 保存第一个密文下标
            int temp2 = -1;// 保存第二个密文下标
            int temp3 = -1;// 保存第三个密文下标
            int temp4 = -1;// 保存第四个密文下标

            // 通过码表反查密文对应的下标,然后分别获得十进制数字
            for (; index < 64; index++) {
                if (b_flag[i] == mb[index]) {
                    temp1 = index;
                }
                if (b_flag[i + 1] == mb[index]) {
                    temp2 = index;
                }
                if (b_flag[i + 2] == mb[index]) {
                    temp3 = index;
                }
                if (b_flag[i + 3] == mb[index])// 一轮密文4个字节已取出,退出循环
                {
                    temp4 = index;
                }

                // 如果已经查到四个下标就退出循环!
                if (temp1 != -1 && temp2 != -1 && temp3 != -1 && temp4 != -1)
                    break;
            }

            if (temp3 != -1 && temp4 != -1)// 完整的读取到了4个字节,直接解密
            {
                f_flag.append((char) ((temp1 << 2) | (temp2 >> 4)));// 取第一个密文6个字符再加上第二个密文前2个字符
                f_flag.append((char) (((temp2 & 15) << 4) | (temp3 >> 2))); // 取第二个密文后4个字符再加上第三个密文前4个字符
                f_flag.append((char) (((temp3 & 3) << 6) | temp4)); // 取第三个密文后2个字符再加上第四个密文6个字符
            } else if (temp3 == -1)// 只取到了2个字节
            {
                f_flag.append((char) ((temp1 << 2) | (temp2 >> 4))); // 取第一个密文6个字符再加上第二个密文前2个字符
            } else if (temp4 == -1)// 只取到了3个字节
            {
                f_flag.append((char) ((temp1 << 2) | (temp2 >> 4)));// 取第一个密文6个字符再加上第二个密文前2个字符
                f_flag.append((char) ((temp2 & 15) << 4 | (temp3 >> 2)));// 取第二个密文后4个字符第三个密文前4个字符
            } else {
                System.out.println("下标取值不对,程序逻辑错误!!!");
            }
            i += 4;// 密文每次循环向后移动4位
        }
        System.out.println("flag3:" + f_flag);

    }
}

  6.运行,得到flag:“flag{justANot#er@p3}”。

flag1:bM3TQsXg30i9g3==QAoOQMPFks1BsB7c
flag2:QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
flag3:flag{just_ANot#er_@p3}

免费评分

参与人数 4吾爱币 +3 热心值 +4 收起 理由
xyqrm + 1 我很赞同!
bjznhxy + 1 + 1 谢谢@Thanks!
laoxiao + 1 + 1 用心讨论,共获提升!
自渡. + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

byl20com 发表于 2022-8-10 12:16
非常有用的程序
moonway 发表于 2022-8-10 13:01
iawyxkdn8 发表于 2022-8-10 13:14
zouwei20081305 发表于 2022-8-10 14:05

真是牛人啊
yiqibufenli 发表于 2022-8-10 14:10
能够坚持不懈就是最棒的, 佩服
laoxiao 发表于 2022-8-10 14:20
你的认真与坚持令人感动,羡慕你已经成为大师啦,向你学习并致敬
tzl0310 发表于 2022-8-10 15:55
感谢讲解
学习使我快乐1 发表于 2022-8-10 15:57
谢谢分享
bjznhxy 发表于 2022-8-10 15:59
谢谢作者分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 14:07

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表