## 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。
  6.再看第二个do while循环v9每次自增2,把中间三行挑出来单独看,这不就是典型的交换两个变量的值嘛,那这里就是每次循环取v12中的两个值,然后交换,最终和"MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"比较,相等,就说明验证通过了。
- v13 = v12[v9];
- v12[v9] = v12[v9 + 1];
- v12[v9 + 1] = v13;
复制代码
## 0x2 动态分析
  1.运行一下程序,用JEB动态调试看看,发现报错了,原来是上次把root权限给关了造成的,重新打开即可。
  2.CTRL+B在jeb中打上断点,将apk拖进jeb,然后在输入123456点check按钮,断了下来,直接运行到函数末尾,看右侧变量,可以知道我输入的123456,经过base64编码得到:"f4G91LqC"。
(
  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++版搬运过来即可。
  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{just_ANot#er_@p3}”。
- flag1:bM3TQsXg30i9g3==QAoOQMPFks1BsB7c
- flag2:QAoOQMPFks1BsB7cbM3TQsXg30i9g3==
- flag3:flag{just_ANot#er_@p3}
复制代码
|
使用道具 举报