CTF-RE: 安卓逆向 + 加密算法分析爆破 [第一届国城杯 round] 赛后学习

step 1

运行程序,发现是一个登录框,找到主函数

package com.example.demo;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private EditText Password;  // 密码输入框
    private EditText User;      // 用户名输入框
    private Button button;      // 按钮

    // 验证输入的字符串是否只包含字母和下划线
    private boolean isValidInput(String s) {
        return s.matches("[a-zA-Z_]*");  // 正则表达式:只允许字母和下划线
    }

    @Override
    protected void onCreate(Bundle bundle0) {
        super.onCreate(bundle0);
        this.setContentView(R.layout.activity_main);  // 设置活动布局,使用activity_main.xml作为布局文件

        // 初始化UI组件
        this.button = (Button)this.findViewById(R.id.button);  // 按钮
        this.User = (EditText)this.findViewById(R.id.username);  // 用户名输入框
        this.Password = (EditText)this.findViewById(R.id.password);  // 密码输入框

        // 设置按钮的点击事件监听器
        this.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view0) {
                MakePath makePath0 = new MakePath();  // 创建MakePath对象,用于路径处理
                Round round0 = new Round();  // 创建Round对象,用于密码验证
                
                // 获取输入的用户名和密码
                String s = MainActivity.this.User.getText().toString();  // 获取用户名
                String s1 = MainActivity.this.Password.getText().toString();  // 获取密码
                
                // 检查用户名和密码是否合法
                if (MainActivity.this.isValidInput(s) && MainActivity.this.isValidInput(s1)) {
                    // 如果用户名符合条件,继续处理密码验证
                    if (makePath0.encodeToBase64(s).equals("c9m1bRmfY5Wk")) {  // 检查用户名是否匹配已知的Base64编码值
                        // 如果用户名匹配,再检查密码是否正确
                        if (round0.round(makePath0.encode(MainActivity.this, s), s1)) {
                            // 密码正确,显示成功消息
                            Toast.makeText(MainActivity.this, "That's right! You have found it, the flag is D0g3xGC{" + s + s1 + "}", Toast.LENGTH_LONG).show();
                            return;
                        }
                        // 密码错误,显示失败消息
                        Toast.makeText(MainActivity.this, "Wrong! Your password is incorrect", Toast.LENGTH_LONG).show();
                        return;
                    }
                    // 用户名错误,显示失败消息
                    Toast.makeText(MainActivity.this, "Wrong! Your username is incorrect", Toast.LENGTH_LONG).show();
                    return;
                }
                // 如果输入的用户名或密码无效,显示提示信息
                Toast.makeText(MainActivity.this, "Invalid input! Only lowercase letters and uppercase letters and '_' are allowed.", Toast.LENGTH_LONG).show();
            }
        });
    }
}

看一眼encodeToBase64()

package com.example.demo;

import android.content.Context;

public class MakePath {
    // 定义一个字符数组,用于Base64编码的字符集
    private static final char[] BASE64_CHARS;

    static {
        // 初始化BASE64字符集,包括大写字母、小写字母、数字和 "+"、"/"
        MakePath.BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
    }

    // Makebox 方法,接受一个字符串并生成一个整数数组
    // 该方法的功能是通过某种方式对传入字符串的每个字符进行操作
    public int[] Makebox(String s) {
        // 创建一个长度为0x400(1024)的整数数组
        int[] arr_v = new int[0x400];

        // 将整数0到0x3FF填充到数组的反向位置
        for (int v1 = 0; v1 < 0x400; ++v1) {
            arr_v[0x3FF - v1] = v1;
        }

        // 遍历数组并与字符串中的字符进行异或运算,生成新的数组
        // 这里通过对字符串每个字符的值与当前位置进行异或,得到一个加密后的结果
        for (int v = 0; v < 0x400; ++v) {
            arr_v[v] ^= s.charAt(v % s.length());
        }

        // 返回处理后的整数数组
        return arr_v;
    }

    // encode 方法,接受一个 Context 对象和一个字符串,并返回经过加密处理后的整数数组
    public int[] encode(Context context0, String s) {
        // 先将字符串进行 Base64 编码,再传递给 Makebox 方法生成加密数组
        return this.Makebox(this.encodeToBase64(s));
    }

    // encodeToBase64 方法,将输入字符串编码为 Base64 格式的字符串
    public String encodeToBase64(String s) {
        StringBuilder stringBuilder0 = new StringBuilder();
        // 将字符串转换为字节数组
        byte[] arr_b = s.getBytes();

        // 计算需要补充的字节数,使得字节数组长度是3的倍数
        int v = (3 - arr_b.length % 3) % 3;
        // 计算 Base64 编码后所需的总字节长度
        int v1 = arr_b.length + v;

        // 遍历字节数组,每三字节为一组,进行编码
        for (int v2 = 0; v2 < v1; v2 += 3) {
            // 组合三个字节为一个24位的整数
            int v3 = 0;
            for (int v4 = 0; v4 < 3; ++v4) {
                v3 <<= 8;
                int v5 = v2 + v4;
                if (v5 < arr_b.length) {
                    v3 |= arr_b[v5] & 0xFF; // 将字节拼接成整数
                }
            }

            // 将24位整数分割成4个6位的部分,每部分对应一个 Base64 字符
            for (int v6 = 0; true; ++v6) {
                int v7 = 2;
                if (v6 >= 4) { // 每次循环最多生成4个Base64字符
                    break;
                }
                if (v6 != 1) {
                    v7 = v6 == 2 ? 1 : v6;
                }

                // 根据当前位的值生成Base64字符
                // 当处理的数据比原始数据少时,用 '=' 作为填充字符
                if (v2 * 8 / 6 + v6 < arr_b.length * 8 / 6 + v) {
                    stringBuilder0.append(MakePath.BASE64_CHARS[v3 >> (3 - v7) * 6 & 0x3F]);
                } else {
                    stringBuilder0.append('='); // 填充字符
                }
            }
        }

        // 返回最终的 Base64 编码字符串
        return stringBuilder0.toString();
    }
}

这里很奇怪

                if (v6 != 1) {
                    v7 = v6 == 2 ? 1 : v6;
                }

如果 v6 != 1 且 v6 = 2 , v7 = 6 ,否则 v7 = 1

代码可以拉到本地直接跑

class MakePath {  
    private static char[] BASE64_CHARS;  
  
    static {  
        MakePath.BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();  
    }  
  
    public static String encodeToBase64(String s) {  
        StringBuilder stringBuilder0 = new StringBuilder();  
        byte[] arr_b = s.getBytes();  
        int v = (3 - arr_b.length % 3) % 3;  
        int v1 = arr_b.length + v;  
        for(int v2 = 0; v2 < v1; v2 += 3) {  
            int v3 = 0;  
            for(int v4 = 0; v4 < 3; ++v4) {  
                v3 <<= 8;  
                int v5 = v2 + v4;  
                if(v5 < arr_b.length) {  
                    v3 |= arr_b[v5] & 0xFF;  
                }  
            }  
  
            for(int v6 = 0; true; ++v6) {  
                int v7 = 2;  
                if(v6 >= 4) {  
                    break;  
                }  
  
                if(v6 != 1) {  
                    v7 = v6 == 2 ? 1 : v6;  
                }  
  
                if(v2 * 8 / 6 + v6 < arr_b.length * 8 / 6 + v) {  
                    stringBuilder0.append(MakePath.BASE64_CHARS[v3 >> (3 - v7) * 6 & 0x3F]);  
                }  
                else {  
                    stringBuilder0.append('=');  
                }  
            }  
        }  
  
        return stringBuilder0.toString();  
    }  
}  
  
public class Main {  
    public static void main(String[] args) {  
        String input = "Hello World";  
        String encoded = MakePath.encodeToBase64(input);  
        System.out.println("Encoded: " + encoded);  
        }  
}

Hello World常规编码后应该是SGVsbG8gV29ybGQ=

跟程序输出的对比一下

SGVsbG8gV29ybGQ=
SVGsb8GgV92ybQG=

再编一个别的tes 应该是 dGVz

程序出来是

dVGz

不难看出来调换了每四字节的 1,2 位

c9m1bRmfY5Wk

改一下c9m1bRmfY5Wk

cm91bmRfYW5k

解密得到

round_and

step 2

继续来到

round0.round(makePath0.encode(MainActivity.this, s), s1)

这里把你输入的user生成一个s盒,作为参数和password一起传到round,我们先dump替换盒

import java.util.Arrays;  
  
class MakePath {  
    private static char[] BASE64_CHARS;  
  
    static {  
        MakePath.BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();  
    }  
  
    public int[] Makebox(String s) {  
        int[] arr_v = new int[0x400];  
        for(int v1 = 0; v1 < 0x400; ++v1) {  
            arr_v[0x3FF - v1] = v1;  
        }  
  
        for(int v = 0; v < 0x400; ++v) {  
            arr_v[v] ^= s.charAt(v % s.length());  
        }  
  
        return arr_v;  
    }  
  
    public int[] encode(String s) {  
        return this.Makebox(this.encodeToBase64(s));  
    }  
  
    public String encodeToBase64(String s) {  
        StringBuilder stringBuilder0 = new StringBuilder();  
        byte[] arr_b = s.getBytes();  
        int v = (3 - arr_b.length % 3) % 3;  
        int v1 = arr_b.length + v;  
        for(int v2 = 0; v2 < v1; v2 += 3) {  
            int v3 = 0;  
            for(int v4 = 0; v4 < 3; ++v4) {  
                v3 <<= 8;  
                int v5 = v2 + v4;  
                if(v5 < arr_b.length) {  
                    v3 |= arr_b[v5] & 0xFF;  
                }  
            }  
  
            for(int v6 = 0; true; ++v6) {  
                int v7 = 2;  
                if(v6 >= 4) {  
                    break;  
                }  
  
                if(v6 != 1) {  
                    v7 = v6 == 2 ? 1 : v6;  
                }  
  
                if(v2 * 8 / 6 + v6 < arr_b.length * 8 / 6 + v) {  
                    stringBuilder0.append(MakePath.BASE64_CHARS[v3 >> (3 - v7) * 6 & 0x3F]);  
                }  
                else {  
                    stringBuilder0.append('=');  
                }  
            }  
        }  
  
        return stringBuilder0.toString();  
    }  
}  
  
public class Main {  
    public static void main(String[] args) {  
        int[] rarry;  
        MakePath MakePath = new MakePath();  
        rarry = MakePath.encode("round_and");  
        System.out.println(Arrays.toString(rarry));  
    }  
}

PS:我个人认为本地测代码比frida方便一点,可以本地单步,所以没用frida.

[924, 967, 912, 973, 921, 936, 916, 926, 942, 963, 930, 927, 912, 971, 924, 961, 909, 956, 896, 906, 946, 991, 958, 899, 900, 991, 904, 981, 897, 944, 908, 902, 902, 1003, 906, 951, 952, 995, 948, 1001, 949, 900, 952, 946, 906, 999, 902, 955, 940, 1015, 928, 1021, 937, 920, 932, 942, 926, 1011, 914, 943, 928, 1019, 940, 1009, 989, 1004, 976, 986, 994, 911, 1006, 979, 980, 911, 984, 901, 977, 992, 988, 982, 1014, 923, 1018, 967, 968, 915, 964, 921, 965, 1012, 968, 962, 1018, 919, 1014, 971, 1020, 935, 1008, 941, 1017, 968, 1012, 1022, 974, 931, 962, 1023, 1008, 939, 1020, 929, 1005, 988, 992, 1002, 978, 959, 990, 995, 996, 959, 1000, 949, 993, 976, 1004, 998, 806, 843, 810, 791, 792, 835, 788, 841, 789, 804, 792, 786, 810, 839, 806, 795, 780, 855, 768, 861, 777, 824, 772, 782, 830, 851, 818, 783, 768, 859, 780, 849, 829, 780, 816, 826, 770, 879, 782, 819, 820, 879, 824, 869, 817, 768, 828, 822, 790, 891, 794, 807, 808, 883, 804, 889, 805, 788, 808, 802, 794, 887, 790, 811, 860, 775, 848, 781, 857, 872, 852, 862, 878, 771, 866, 863, 848, 779, 860, 769, 845, 892, 832, 842, 882, 799, 894, 835, 836, 799, 840, 789, 833, 880, 844, 838, 838, 811, 842, 887, 888, 803, 884, 809, 885, 836, 888, 882, 842, 807, 838, 891, 876, 823, 864, 829, 873, 856, 868, 878, 862, 819, 850, 879, 864, 827, 876, 817, 669, 684, 656, 666, 674, 719, 686, 659, 660, 719, 664, 709, 657, 672, 668, 662, 694, 731, 698, 647, 648, 723, 644, 729, 645, 692, 648, 642, 698, 727, 694, 651, 700, 743, 688, 749, 697, 648, 692, 702, 654, 739, 642, 703, 688, 747, 700, 737, 685, 668, 672, 682, 658, 767, 670, 675, 676, 767, 680, 757, 673, 656, 684, 678, 742, 651, 746, 727, 728, 643, 724, 649, 725, 740, 728, 722, 746, 647, 742, 731, 716, 663, 704, 669, 713, 760, 708, 718, 766, 659, 754, 719, 704, 667, 716, 657, 765, 716, 752, 762, 706, 687, 718, 755, 756, 687, 760, 677, 753, 704, 764, 758, 726, 699, 730, 743, 744, 691, 740, 697, 741, 724, 744, 738, 730, 695, 726, 747, 540, 583, 528, 589, 537, 552, 532, 542, 558, 579, 546, 543, 528, 587, 540, 577, 525, 572, 512, 522, 562, 607, 574, 515, 516, 607, 520, 597, 513, 560, 524, 518, 518, 619, 522, 567, 568, 611, 564, 617, 565, 516, 568, 562, 522, 615, 518, 571, 556, 631, 544, 637, 553, 536, 548, 558, 542, 627, 530, 559, 544, 635, 556, 625, 605, 620, 592, 602, 610, 527, 622, 595, 596, 527, 600, 517, 593, 608, 604, 598, 630, 539, 634, 583, 584, 531, 580, 537, 581, 628, 584, 578, 634, 535, 630, 587, 636, 551, 624, 557, 633, 584, 628, 638, 590, 547, 578, 639, 624, 555, 636, 545, 621, 604, 608, 618, 594, 575, 606, 611, 612, 575, 616, 565, 609, 592, 620, 614, 422, 459, 426, 407, 408, 451, 404, 457, 405, 420, 408, 402, 426, 455, 422, 411, 396, 471, 384, 477, 393, 440, 388, 398, 446, 467, 434, 399, 384, 475, 396, 465, 445, 396, 432, 442, 386, 495, 398, 435, 436, 495, 440, 485, 433, 384, 444, 438, 406, 507, 410, 423, 424, 499, 420, 505, 421, 404, 424, 418, 410, 503, 406, 427, 476, 391, 464, 397, 473, 488, 468, 478, 494, 387, 482, 479, 464, 395, 476, 385, 461, 508, 448, 458, 498, 415, 510, 451, 452, 415, 456, 405, 449, 496, 460, 454, 454, 427, 458, 503, 504, 419, 500, 425, 501, 452, 504, 498, 458, 423, 454, 507, 492, 439, 480, 445, 489, 472, 484, 494, 478, 435, 466, 495, 480, 443, 492, 433, 285, 300, 272, 282, 290, 335, 302, 275, 276, 335, 280, 325, 273, 288, 284, 278, 310, 347, 314, 263, 264, 339, 260, 345, 261, 308, 264, 258, 314, 343, 310, 267, 316, 359, 304, 365, 313, 264, 308, 318, 270, 355, 258, 319, 304, 363, 316, 353, 301, 284, 288, 298, 274, 383, 286, 291, 292, 383, 296, 373, 289, 272, 300, 294, 358, 267, 362, 343, 344, 259, 340, 265, 341, 356, 344, 338, 362, 263, 358, 347, 332, 279, 320, 285, 329, 376, 324, 334, 382, 275, 370, 335, 320, 283, 332, 273, 381, 332, 368, 378, 322, 303, 334, 371, 372, 303, 376, 293, 369, 320, 380, 374, 342, 315, 346, 359, 360, 307, 356, 313, 357, 340, 360, 354, 346, 311, 342, 363, 156, 199, 144, 205, 153, 168, 148, 158, 174, 195, 162, 159, 144, 203, 156, 193, 141, 188, 128, 138, 178, 223, 190, 131, 132, 223, 136, 213, 129, 176, 140, 134, 134, 235, 138, 183, 184, 227, 180, 233, 181, 132, 184, 178, 138, 231, 134, 187, 172, 247, 160, 253, 169, 152, 164, 174, 158, 243, 146, 175, 160, 251, 172, 241, 221, 236, 208, 218, 226, 143, 238, 211, 212, 143, 216, 133, 209, 224, 220, 214, 246, 155, 250, 199, 200, 147, 196, 153, 197, 244, 200, 194, 250, 151, 246, 203, 252, 167, 240, 173, 249, 200, 244, 254, 206, 163, 194, 255, 240, 171, 252, 161, 237, 220, 224, 234, 210, 191, 222, 227, 228, 191, 232, 181, 225, 208, 236, 230, 38, 75, 42, 23, 24, 67, 20, 73, 21, 36, 24, 18, 42, 71, 38, 27, 12, 87, 0, 93, 9, 56, 4, 14, 62, 83, 50, 15, 0, 91, 12, 81, 61, 12, 48, 58, 2, 111, 14, 51, 52, 111, 56, 101, 49, 0, 60, 54, 22, 123, 26, 39, 40, 115, 36, 121, 37, 20, 40, 34, 26, 119, 22, 43, 92, 7, 80, 13, 89, 104, 84, 94, 110, 3, 98, 95, 80, 11, 92, 1, 77, 124, 64, 74, 114, 31, 126, 67, 68, 31, 72, 21, 65, 112, 76, 70, 70, 43, 74, 119, 120, 35, 116, 41, 117, 68, 120, 114, 74, 39, 70, 123, 108, 55, 96, 61, 105, 88, 100, 110, 94, 51, 82, 111, 96, 59, 108, 49]

step 5 逆向算法分析

核心代码再这部分

int v1 = 33; // 初始化索引值为 33  
// 遍历输入字符串的每个字符  
for(int v2 = 0; v2 < s.length(); ++v2) {  
    int v3 = s.charAt(v2); // 获取当前字符的 ASCII 值  
  
    // 对当前字符执行 32 次操作(可能是加密的一个步骤)  
    for(int v4 = 0; v4 < 0x20; ++v4) {  
        // 根据当前状态数组和字符值决定执行哪种操作  
        switch(((arr_v[v1] ^ v3) % 5 + 5) % 5) {  
            case 0: {  
                // 执行加法操作  
                round$Result0 = this.add(arr_v, v3, v1);  
                break;  
            }  
            case 1: {  
                // 执行减法操作  
                round$Result0 = this.sub(arr_v, v3, v1);  
                break;  
            }  
            case 2: {  
                // 执行异或操作  
                round$Result0 = this.xor(arr_v, v3, v1);  
                break;  
            }  
            case 3: {  
                // 执行左移操作  
                round$Result0 = this.shl(v3, v1);  
                break;  
            }  
            case 4: {  
                // 执行右移操作  
                round$Result0 = this.shr(v3, v1);  
                break;  
            }  
            default: {  
                // 默认情况下,不做任何操作  
                round$Result0 = new Result(v3, v1);  
            }  
        }  
  
        // 更新字符值和索引值为操作后的结果  
        v3 = round$Result0.getNum();  
        v1 = round$Result0.getRip();  
    }  
  
    // 将最终处理后的字符值存储到 arr_v1 数组中  
    arr_v1[v2] = v3;  
}

我们可以侦测每次返回值爆破(爆破好多次都进死路让ai写了个回溯),或者限制字符然后手动pass也行

import java.util.ArrayList;  
import java.util.List;  
  
class Round {  
    public static class Result {  
        private int num;  
        private int rip;  
  
        public Result(int v, int v1) {  
            this.num = v;  
            this.rip = v1;  
        }  
  
        public int getNum() {  
            return this.num;  
        }  
  
        public int getRip() {  
            return this.rip;  
        }  
    }  
  
    public Result add(int[] arr_v, int v, int v1) {  
        int v2 = ((v + arr_v[v1]) % 0x400 + 0x400) % 0x400;  
        return new Result(v2, (v1 + v2) % 0x400);  
    }  
  
    public int[] round(int[] box, String s) {  
        Result round$Result0;  
        int input_len = s.length();  
        int[] out = new int[input_len];  
        int v1 = 33;  
        for(int i = 0; i < s.length(); ++i) {  
            int v3 = s.charAt(i);  
            for(int v4 = 0; v4 < 0x20; ++v4) {  
                switch(((box[v1] ^ v3) % 5 + 5) % 5) {  
                    case 0: {  
                        round$Result0 = this.add(box, v3, v1);  
                        break;  
                    }  
                    case 1: {  
                        round$Result0 = this.sub(box, v3, v1);  
                        break;  
                    }  
                    case 2: {  
                        round$Result0 = this.xor(box, v3, v1);  
                        break;  
                    }  
                    case 3: {  
                        round$Result0 = this.shl(v3, v1);  
                        break;  
                    }  
                    case 4: {  
                        round$Result0 = this.shr(v3, v1);  
                        break;  
                    }  
                    default: {  
                        round$Result0 = new Result(v3, v1);  
                    }  
                }  
  
                v3 = round$Result0.getNum();  
                v1 = round$Result0.getRip();  
            }  
  
            out[i] = v3;  
        }  
        return out;  
    }  
  
    public Result shl(int v, int v1) {  
        int v2 = (v >> 3) % 0x400;  
        return new Result(v2, (v1 + v2) % 0x400);  
    }  
  
    public Result shr(int v, int v1) {  
        int v2 = (v << 3) % 0x400;  
        return new Result(v2, (v1 + v2) % 0x400);  
    }  
  
    public Result sub(int[] arr_v, int v, int v1) {  
        int v2 = ((v - arr_v[v1]) % 0x400 + 0x400) % 0x400;  
        return new Result(v2, (v1 + v2) % 0x400);  
    }  
  
    public Result xor(int[] arr_v, int v, int v1) {  
        int v2 = (arr_v[v1] ^ v) % 0x400;  
        return new Result(v2, (v1 + v2) % 0x400);  
    }  
}  
  
public class Main {  
    public static void main(String[] args) {  
        // Initialize the Round class  
        Round round = new Round();  
  
        // Target values to match  
        int[] end = {0x160, 646, 0x2F0, 882, 65, 0, 0x7A, 0, 0, 7, 350, 360};  
  
        // Assuming 'box' is not required or is used within Round.round appropriately  
        int[] box = {924, 967, 912, 973, 921, 936, 916, 926, 942, 963, 930, 927, 912, 971, 924, 961, 909, 956, 896, 906, 946, 991, 958, 899, 900, 991, 904, 981, 897, 944, 908, 902, 902, 1003, 906, 951, 952, 995, 948, 1001, 949, 900, 952, 946, 906, 999, 902, 955, 940, 1015, 928, 1021, 937, 920, 932, 942, 926, 1011, 914, 943, 928, 1019, 940, 1009, 989, 1004, 976, 986, 994, 911, 1006, 979, 980, 911, 984, 901, 977, 992, 988, 982, 1014, 923, 1018, 967, 968, 915, 964, 921, 965, 1012, 968, 962, 1018, 919, 1014, 971, 1020, 935, 1008, 941, 1017, 968, 1012, 1022, 974, 931, 962, 1023, 1008, 939, 1020, 929, 1005, 988, 992, 1002, 978, 959, 990, 995, 996, 959, 1000, 949, 993, 976, 1004, 998, 806, 843, 810, 791, 792, 835, 788, 841, 789, 804, 792, 786, 810, 839, 806, 795, 780, 855, 768, 861, 777, 824, 772, 782, 830, 851, 818, 783, 768, 859, 780, 849, 829, 780, 816, 826, 770, 879, 782, 819, 820, 879, 824, 869, 817, 768, 828, 822, 790, 891, 794, 807, 808, 883, 804, 889, 805, 788, 808, 802, 794, 887, 790, 811, 860, 775, 848, 781, 857, 872, 852, 862, 878, 771, 866, 863, 848, 779, 860, 769, 845, 892, 832, 842, 882, 799, 894, 835, 836, 799, 840, 789, 833, 880, 844, 838, 838, 811, 842, 887, 888, 803, 884, 809, 885, 836, 888, 882, 842, 807, 838, 891, 876, 823, 864, 829, 873, 856, 868, 878, 862, 819, 850, 879, 864, 827, 876, 817, 669, 684, 656, 666, 674, 719, 686, 659, 660, 719, 664, 709, 657, 672, 668, 662, 694, 731, 698, 647, 648, 723, 644, 729, 645, 692, 648, 642, 698, 727, 694, 651, 700, 743, 688, 749, 697, 648, 692, 702, 654, 739, 642, 703, 688, 747, 700, 737, 685, 668, 672, 682, 658, 767, 670, 675, 676, 767, 680, 757, 673, 656, 684, 678, 742, 651, 746, 727, 728, 643, 724, 649, 725, 740, 728, 722, 746, 647, 742, 731, 716, 663, 704, 669, 713, 760, 708, 718, 766, 659, 754, 719, 704, 667, 716, 657, 765, 716, 752, 762, 706, 687, 718, 755, 756, 687, 760, 677, 753, 704, 764, 758, 726, 699, 730, 743, 744, 691, 740, 697, 741, 724, 744, 738, 730, 695, 726, 747, 540, 583, 528, 589, 537, 552, 532, 542, 558, 579, 546, 543, 528, 587, 540, 577, 525, 572, 512, 522, 562, 607, 574, 515, 516, 607, 520, 597, 513, 560, 524, 518, 518, 619, 522, 567, 568, 611, 564, 617, 565, 516, 568, 562, 522, 615, 518, 571, 556, 631, 544, 637, 553, 536, 548, 558, 542, 627, 530, 559, 544, 635, 556, 625, 605, 620, 592, 602, 610, 527, 622, 595, 596, 527, 600, 517, 593, 608, 604, 598, 630, 539, 634, 583, 584, 531, 580, 537, 581, 628, 584, 578, 634, 535, 630, 587, 636, 551, 624, 557, 633, 584, 628, 638, 590, 547, 578, 639, 624, 555, 636, 545, 621, 604, 608, 618, 594, 575, 606, 611, 612, 575, 616, 565, 609, 592, 620, 614, 422, 459, 426, 407, 408, 451, 404, 457, 405, 420, 408, 402, 426, 455, 422, 411, 396, 471, 384, 477, 393, 440, 388, 398, 446, 467, 434, 399, 384, 475, 396, 465, 445, 396, 432, 442, 386, 495, 398, 435, 436, 495, 440, 485, 433, 384, 444, 438, 406, 507, 410, 423, 424, 499, 420, 505, 421, 404, 424, 418, 410, 503, 406, 427, 476, 391, 464, 397, 473, 488, 468, 478, 494, 387, 482, 479, 464, 395, 476, 385, 461, 508, 448, 458, 498, 415, 510, 451, 452, 415, 456, 405, 449, 496, 460, 454, 454, 427, 458, 503, 504, 419, 500, 425, 501, 452, 504, 498, 458, 423, 454, 507, 492, 439, 480, 445, 489, 472, 484, 494, 478, 435, 466, 495, 480, 443, 492, 433, 285, 300, 272, 282, 290, 335, 302, 275, 276, 335, 280, 325, 273, 288, 284, 278, 310, 347, 314, 263, 264, 339, 260, 345, 261, 308, 264, 258, 314, 343, 310, 267, 316, 359, 304, 365, 313, 264, 308, 318, 270, 355, 258, 319, 304, 363, 316, 353, 301, 284, 288, 298, 274, 383, 286, 291, 292, 383, 296, 373, 289, 272, 300, 294, 358, 267, 362, 343, 344, 259, 340, 265, 341, 356, 344, 338, 362, 263, 358, 347, 332, 279, 320, 285, 329, 376, 324, 334, 382, 275, 370, 335, 320, 283, 332, 273, 381, 332, 368, 378, 322, 303, 334, 371, 372, 303, 376, 293, 369, 320, 380, 374, 342, 315, 346, 359, 360, 307, 356, 313, 357, 340, 360, 354, 346, 311, 342, 363, 156, 199, 144, 205, 153, 168, 148, 158, 174, 195, 162, 159, 144, 203, 156, 193, 141, 188, 128, 138, 178, 223, 190, 131, 132, 223, 136, 213, 129, 176, 140, 134, 134, 235, 138, 183, 184, 227, 180, 233, 181, 132, 184, 178, 138, 231, 134, 187, 172, 247, 160, 253, 169, 152, 164, 174, 158, 243, 146, 175, 160, 251, 172, 241, 221, 236, 208, 218, 226, 143, 238, 211, 212, 143, 216, 133, 209, 224, 220, 214, 246, 155, 250, 199, 200, 147, 196, 153, 197, 244, 200, 194, 250, 151, 246, 203, 252, 167, 240, 173, 249, 200, 244, 254, 206, 163, 194, 255, 240, 171, 252, 161, 237, 220, 224, 234, 210, 191, 222, 227, 228, 191, 232, 181, 225, 208, 236, 230, 38, 75, 42, 23, 24, 67, 20, 73, 21, 36, 24, 18, 42, 71, 38, 27, 12, 87, 0, 93, 9, 56, 4, 14, 62, 83, 50, 15, 0, 91, 12, 81, 61, 12, 48, 58, 2, 111, 14, 51, 52, 111, 56, 101, 49, 0, 60, 54, 22, 123, 26, 39, 40, 115, 36, 121, 37, 20, 40, 34, 26, 119, 22, 43, 92, 7, 80, 13, 89, 104, 84, 94, 110, 3, 98, 95, 80, 11, 92, 1, 77, 124, 64, 74, 114, 31, 126, 67, 68, 31, 72, 21, 65, 112, 76, 70, 70, 43, 74, 119, 120, 35, 116, 41, 117, 68, 120, 114, 74, 39, 70, 123, 108, 55, 96, 61, 105, 88, 100, 110, 94, 51, 82, 111, 96, 59, 108, 49};  
  
        // StringBuilder to build the current string  
        StringBuilder baseStr = new StringBuilder();  
  
        // List to store all valid results  
        List<String> validResults = new ArrayList<>();  
  
        // Start the backtracking process  
        System.out.println("Starting backtracking search...");  
        backtrack(round, box, baseStr, 0, end, validResults);  
  
        // Output all valid results  
        if (validResults.isEmpty()) {  
            System.out.println("No valid character combinations found.");  
        } else {  
            System.out.println("Valid character combinations found:");  
            for (String result : validResults) {  
                System.out.println(result);  
            }  
        }  
    }  
  
    /**  
     * Backtrack function to generate and validate character combinations.     *     * @param round        Instance of the Round class  
     * @param box          An array required by Round.round method  
     * @param baseStr      StringBuilder to accumulate characters  
     * @param index        Current index (depth) in the backtracking tree  
     * @param end          Target array to match  
     * @param validResults List to store valid combinations  
     */    public static void backtrack(Round round, int[] box, StringBuilder baseStr, int index, int[] end, List<String> validResults) {  
        if (index == 12) {  
            // Base case: Completed a 12-character string  
            String candidate = baseStr.toString();  
            System.out.println("Attempting string: " + candidate);  
            int[] ret = round.round(box, candidate);  
            if (isValidResult(ret, end)) {  
                System.out.println("Valid combination found: " + candidate);  
                validResults.add(candidate);  
            } else {  
                System.out.println("Invalid combination: " + candidate);  
            }  
            return;  
        }  
  
        // Iterate through all printable ASCII characters  
        for (int f = 32; f <= 126; f++) {  
            char tmp = (char) f;    // Current character to attempt  
            baseStr.append(tmp);    // Add character to the current string  
  
            System.out.println("Selected character '" + tmp + "', current string: " + baseStr.toString());  
  
            // Compute 'ret' for the current partial string  
            int[] ret = round.round(box, baseStr.toString());  
  
            // Check if the current 'ret' matches 'end' up to the current index  
            if (isValidPartial(ret, end, index + 1)) {  
                // If valid, proceed to the next character  
                backtrack(round, box, baseStr, index + 1, end, validResults);  
            } else {  
                // If not valid, prune this path  
                System.out.println("Pruning path: " + baseStr.toString());  
            }  
  
            // Backtrack: Remove the last character and continue  
            baseStr.deleteCharAt(baseStr.length() - 1);  
            System.out.println("Backtracking, removed character '" + tmp + "', current string: " + baseStr.toString());  
        }  
    }  
  
    /**  
     * Checks if the computed 'ret' array matches the 'end' array exactly.     *     * @param ret Computed array from Round.round  
     * @param end Target array to match  
     * @return true if all elements match; false otherwise  
     */    public static boolean isValidResult(int[] ret, int[] end) {  
        if (ret.length < end.length) {  
            return false;  
        }  
        for (int i = 0; i < end.length; i++) {  
            if (ret[i] != end[i]) {  
                return false;  // Mismatch found  
            }  
        }  
        return true;  // All elements match  
    }  
  
    /**  
     * Checks if the computed 'ret' array matches the 'end' array up to a certain index.     *     * @param ret    Computed array from Round.round  
     * @param end    Target array to match  
     * @param upto   Number of elements to check (from start)  
     * @return true if the first 'upto' elements match; false otherwise  
     */    public static boolean isValidPartial(int[] ret, int[] end, int upto) {  
        if (ret.length < upto) {  
            return false;  
        }  
        for (int i = 0; i < upto; i++) {  
            if (ret[i] != end[i]) {  
                return false;  // Mismatch found in partial match  
            }  
        }  
        return true;  // Partial match is valid  
    }  
}

拼起来就是flag

Toast.makeText(MainActivity.this, "That\'s right! You have found it, the flag is D0g3xGC{" + s + s1 + "}", 1).show();
D0g3xGC{round_and_rounD_we_go}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/943863.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一个简单的机器学习实战例程,使用Scikit-Learn库来完成一个常见的分类任务——**鸢尾花数据集(Iris Dataset)**的分类

机器学习实战通常是将理论与实践结合&#xff0c;通过实际的项目或案例&#xff0c;帮助你理解并应用各种机器学习算法。下面是一个简单的机器学习实战例程&#xff0c;使用Scikit-Learn库来完成一个常见的分类任务——**鸢尾花数据集&#xff08;Iris Dataset&#xff09;**的…

如何解决 ‘adb‘ 不是内部或外部命令,也不是可运行的程序或批处理文件的问题

在cmd中输入 adb &#xff0c;显示 ‘adc‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件的问题 解决办法&#xff1a;在环境变量中添加adb所在的路径 1、找到 adb.exe 的所在的文件路径&#xff0c;一般在 Android 安装目录下 \sdk\platform-tools\adb.exe…

【开源】一款基于SpringBoot的智慧小区物业管理系统

一、下载项目文件 项目文件源码链接&#xff1a;https://pan.quark.cn/s/3998d958e182如出现网盘空间不够存的情况&#xff01;&#xff01;&#xff01;解决办法是先用夸克手机app注册&#xff0c;然后保存上方链接&#xff0c;就可以得到1TB空间了&#xff01;&#xff01;&…

Linux编程(清华大学出版社2019年1月第1版)第7章-进程间通信-课后作业

7.1 输出: 4:ABCD 4:EFGH7.2 输出: numbers3 10 20 30 7.3 #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <limits.h> #include <fcntl.h> #include <sys/types.h> #include <stdint.h> #includ…

线性代数行列式

目录 二阶与三阶行列式 二元线性方程组与二阶行列式 三阶行列式 全排列和对换 排列及其逆序数 对换 n阶行列式的定义 行列式的性质 二阶与三阶行列式 二元线性方程组与二阶行列式 若是采用消元法解x1、x2的话则得到以下式子 有二阶行列式的规律可得&#xff1a;分…

canvas之进度条

canvas之进度条 效果&#xff1a; 封装的组件 <template><div class"circle" :style"{ width: props.radius px, height: props.radius px }"><div class"circle-bg" :style"{ width: props.radius - 5 px, height: pr…

再生核希尔伯特空间(RKHS)上的分位回归

1. 基本定义和理论基础 1.1 再生核希尔伯特空间(RKHS) 给定一个非空集合 X \mathcal{X} X&#xff0c;一个希尔伯特空间 H \mathcal{H} H 称为再生核希尔伯特空间&#xff0c;如果存在一个函数 K : X X → R K: \mathcal{X} \times \mathcal{X} \rightarrow \mathbb{R} K…

Nature+Science=ONNs(光学神经网络)

2024深度学习发论文&模型涨点之——光学神经网络 光学神经网络&#xff08;Optical Neural Networks, ONNs&#xff09;是一种利用光学器件&#xff08;如激光、光学调制器、滤波器、探测器等&#xff09;来模拟和实现神经网络推理功能的计算模型。这种网络通过利用光信号的…

武泳樽携手AI AD Manager荣获红点奖,智能广告管理系统备受瞩目

近日,由著名设计师武泳樽主导设计的AI AD Manager在2024年红点奖评选中荣获大奖,这一殊荣不仅彰显了他在创新设计领域的卓越实力,更巩固了AI AD Manager作为智能广告技术标杆的地位。凭借独特的用户体验设计、尖端的AI驱动功能和出色的技术融合,AI AD Manager在激烈的国际竞争中…

OCR实践-问卷表格统计

前言 书接上文 OCR实践—PaddleOCROCR实践-Table-Transformer 本项目代码已开源 放在 Github上&#xff0c;欢迎参考使用&#xff0c;Star https://github.com/caibucai22/TableAnalysisTool 主要功能说明&#xff1a;对手动拍照的问卷图片进行统计分数&#xff08;对应分数…

flask后端开发(2):URL与视图

目录 URL定义request获取请求参数 gitcode地址&#xff1a; https://gitcode.com/qq_43920838/flask_project.git URL定义 from flask import FlaskappFlask(__name__)app.route(/) def hello_world():return Hello World!app.route(/profile) def profile():return 我是个人…

基于Sentinel的服务保护方案的三种方式(请求限流、线程隔离、服务熔断)超详细讲解

目录 1、三种方式介绍 1.1请求限流 1.2 线程隔离方案 1.3 服务熔断 2、基于sentinel实现 2.1 启动sentinel 2.2 基于springboot整合sentinel 2.2.1请求限流 2.2.2请求隔离 2.2.2.1 OpenFeign整合Sentinel 2.2.3 服务熔断 2.2.3.1 编写降级代码 2.2.3.2 服务熔断 1、…

小程序基础 —— 02 微信小程序账号注册

微信小程序账号注册 小程序开发与网页开发不一样&#xff0c;在开始微信小程序开发之前&#xff0c;需要访问微信公众平台&#xff0c;注册一个微信小程序账号。 有了小程序的账号以后&#xff0c;才可以开发和管理小程序&#xff0c;后续需要通过该账号进行开发信息的设置、…

箭头函数与普通函数的区别

箭头函数&#xff08;Arrow Functions&#xff09;是ES6&#xff08;ECMAScript 2015&#xff09;引入的一种新的函数定义方式&#xff0c;它提供了更简洁的语法和一些与传统函数表达式不同的行为。 以下是箭头函数与普通函数的主要区别&#xff1a; 语法上的简化&#xff1a; …

uniapp实现APP、小程序与webview页面间通讯

需求&#xff1a; 1、需要在Uniapp开发的APP或小程序页面嵌入一个H5网页&#xff0c;需要拿到H5给APP传递的数据。 2、并且这个H5是使用vuevant开发的。&#xff08;其实跟使用uniapp开发H5一样&#xff09; 实现步骤&#xff1a; 1、首先需要兼容多端和App端&#xff0c;因…

iPhone 17 :史诗级大改,120Hz 全面普及

资深果粉应该都听过一个说法&#xff1a;“iPhone 买单不买双”。这个“规律”似乎在iPhone 16上也得到了印证。 近段时间&#xff0c;各方消息都在指明一点&#xff1a;iPhone 16 只是大餐前的小菜&#xff0c;iPhone 17才是真正带来革命性提升的一代神机。下一代 iPhone 17&…

逆袭之路(11)——python网络爬虫:原理、应用、风险与应对策略

困厄铸剑心&#xff0c;逆袭展锋芒。 寒苦凝壮志&#xff0c;腾跃绘华章。 我要逆袭。 目录 一、引言 二、网络爬虫的基本原理 &#xff08;一&#xff09;网络请求与响应 &#xff08;二&#xff09;网页解析 &#xff08;三&#xff09;爬行策略 三、网络爬虫的应用领…

关系数据库

一些关系数据模型的常见概念->数据库概论-CSDN博客 目录 1、关系数据结构 1.1 笛卡尔积 1.2 关系的定义 1.3 关系的性质 2、关系代数 2.1 传统的集合运算 1. 并(union) 2. 交(intersection) 3. 差(difference) 4. 广义笛卡尔积(extended cartesian product) 2.2…

Unity中实现人物残影效果

今天火柴人联盟3公测了&#xff0c;看到一个残影的效果&#xff0c;很有意思&#xff0c;上网查询了一下实现方式&#xff0c; 实现思路&#xff1a; 将角色的网格复制出来&#xff0c;然后放置到新建的物体的MeshFilter组件上&#xff0c;每隔几十毫秒在玩家的位置生成一个&a…

计算机网络习题(第1章 概论 第2章 数据通信基础)

第1章 概论 1、计算机网络 2、互联网 3、计算机网络体系结构 分层模型 OSI/RM 7层模型 TCP/IP 5层模型 协议、PDU、SDU、SAP等术语 数据封装&#xff08;计算&#xff09; 第2章 数据通信基础 1、数据通信系统组成 2、主要性能指标 数据传输速率 码元速率 时延 3…