小菜鸟一枚 发表于 2022-8-10 11:28

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

前言:

  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:(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;
            byte b = 0;
            for (int i2 = 0; i2 <= 2; i2++) {
                if (i + i2 <= bArr.length - 1) {
                  bArr2 = (byte) (b | ((bArr & 255) >>> ((i2 * 2) + 2)));
                  b = (byte) ((((bArr & 255) << (((2 - i2) * 2) + 2)) & 255) >>> 2);
                } else {
                  bArr2 = b;
                  b = 64;
                }
            }
            bArr2 = b;
            for (int i3 = 0; i3 <= 3; i3++) {
                if (bArr2 <= 63) {
                  sb.append(f2481a]);
                } else {
                  sb.append('=');
                }
            }
      }
      return sb.toString();
    }
}
```

&emsp;&emsp;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;
      v12 = v5;
      v8 = v5;
      v7 = v8;
    }
    (*(void (__fastcall **)(int, int, const char *))(*(_DWORD *)a1 + 680))(a1, a3, v5);
    v9 = 0;
    do
    {
      v10 = v9 < 30;
      v13 = v12;
      v12 = v12;
      v12 = 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;
}
```

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

!(https://s1.ax1x.com/2022/08/08/vQOCuD.png)

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

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

!(https://s1.ax1x.com/2022/08/08/vQjJXT.png)

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

!(https://s1.ax1x.com/2022/08/08/vQvMDO.png)

&emsp;&emsp;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端口。

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

## 0x3 计算flag
&emsp;&emsp;1.把"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"作为结果反推。

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

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

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

&emsp;&emsp;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 == mb) {
                                        temp1 = index;
                                }
                                if (b_flag == mb) {
                                        temp2 = index;
                                }
                                if (b_flag == mb) {
                                        temp3 = index;
                                }
                                if (b_flag == mb)// 一轮密文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);

        }
}
```

&emsp;&emsp;6.运行,得到flag:“flag{just_ANot#er_@p3}”。
```
flag1:bM3TQsXg30i9g3==QAoOQMPFks1BsB7c
flag2:QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
flag3:flag{just_ANot#er_@p3}
```

byl20com 发表于 2022-8-10 12:16

非常有用的程序

moonway 发表于 2022-8-10 13:01

牛皮啊!

iawyxkdn8 发表于 2022-8-10 13:14

加油,等你到999天的时候再来看看

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

谢谢作者分享
页: [1] 2 3
查看完整版本: 学破解第198天,《攻防世界mobile练习区easyjni》学习