吾爱破解2024红包题部分WriteUP

查看 119|回复 11
作者:PastYiHJ   
操作日志:24.2.27:合并Web题解
Windows与安卓CM部分
前言
第一年参加红包题目,其中有些操作可能不是那么熟练,感谢各位大佬批评指正,嗯。
(后面的高级题太菜了不会做)
解题领红包之二 {Windows 初级题}
新手题:送分题完成啦来试试新手题吧,点击下方“立即申请”任务,即可获得本题Windows CrackMe题目下载地址,通过分析CrackMe获得本题正确口令的解题方法。
查壳
嗯,很好,没有壳,直接分析。

IDA静态分析
(这是什么鬼啊,撤了撤了)
后来发现ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$?这一串东西是凯撒密码加密过的密文,偏移量为3,但字典是自定义的,懒得跟踪了,直接用OD了
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  unsigned int v4; // ebx
  void **v5; // edi
  int v6; // eax
  void **v7; // ecx
  void **v8; // edx
  unsigned int v9; // esi
  bool v10; // cf
  int v11; // eax
  void *v12; // ecx
  void **v13; // eax
  void *v14; // ecx
  void *Src[5]; // [esp+14h] [ebp-58h] BYREF
  unsigned int v17; // [esp+28h] [ebp-44h]
  void *Block[5]; // [esp+2Ch] [ebp-40h] BYREF
  unsigned int v19; // [esp+40h] [ebp-2Ch]
  void *v20[4]; // [esp+44h] [ebp-28h] BYREF
  int v21; // [esp+54h] [ebp-18h]
  unsigned int v22; // [esp+58h] [ebp-14h]
  int v23; // [esp+68h] [ebp-4h]
  Src[4] = 0;
  v17 = 15;
  LOBYTE(Src[0]) = 0;
  sub_402560(Src, "ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$?", 0x24u);
  v23 = 0;
  SetConsoleTitleA(&ConsoleTitle);
  v21 = 0;
  v22 = 15;
  LOBYTE(v20[0]) = 0;
  LOBYTE(v23) = 1;
  v3 = sub_4027D0();
  sub_402A80(v3);
  sub_4031E0(&dword_42E088, v20);
  v4 = v22;
  v5 = (void **)v20[0];
  if ( v21 == 36 )
  {
    sub_402490(Src);
    sub_401FE0();
    LOBYTE(v23) = 2;
    v7 = Block;
    v8 = v20;
    if ( v19 >= 0x10 )
      v7 = (void **)Block[0];
    if ( v4 >= 0x10 )
      v8 = v5;
    if ( Block[4] == (void *)36 )
    {
      v9 = 32;
      do
      {
        if ( *v8 != *v7 )
          break;
        ++v8;
        ++v7;
        v10 = v9 = 0x10 )
    {
      v12 = Block[0];
      if ( v19 + 1 >= 0x1000 )
      {
        v12 = (void *)*((_DWORD *)Block[0] - 1);
        if ( (unsigned int)(Block[0] - v12 - 4) > 0x1F )
          _invalid_parameter_noinfo_noreturn();
      }
      sub_406010(v12);
    }
    sub_40A6EE("Pause");
  }
  else
  {
    v6 = sub_4027D0();
    sub_402A80(v6);
    sub_40A6EE("Pause");
  }
  if ( v4 >= 0x10 )
  {
    v13 = v5;
    if ( v4 + 1 >= 0x1000 )
    {
      v5 = (void **)*(v5 - 1);
      if ( (unsigned int)((char *)v13 - (char *)v5 - 4) > 0x1F )
        _invalid_parameter_noinfo_noreturn();
    }
    sub_406010(v5);
  }
  if ( v17 >= 0x10 )
  {
    v14 = Src[0];
    if ( v17 + 1 >= 0x1000 )
    {
      v14 = (void *)*((_DWORD *)Src[0] - 1);
      if ( (unsigned int)(Src[0] - v14 - 4) > 0x1F )
        _invalid_parameter_noinfo_noreturn();
    }
    sub_406010(v14);
  }
  return 0;
}
OD动态调试
[ol]
  • 打开OD,导入文件
  • 使用中文搜索引擎,找到一个Success和两个Try again,以及一个Tip

    (Caesar Cipher恺撒密码......怪不得IDA看不懂)
  • 先看看第一个Try again
    上方有个cmp和je,也就是说比较当前字符串长度是否长为36,如果是的话就继续判断,否则就输出 "Error, please try again"

    我们输入36个1,输出变为了"Wrong, please try again",证明上述分析没有问题
  • 找到"Wrong, please try again"所在位置,找到判断条件,下断点,再次输入36个1,在MMX中即可找到CM的flag

    [/ol]

    Flag:fl@g{H@ppy_N3w_e@r!2o24!Fighting!!!}


    解题领红包之三 {Android 初级题}
    题目简介:小明和李华是同学,最近小明发现李华技术进步很快,他太想进步了,于是他一直在观察李华,却发现他老是在玩圈小猫,直到一次偶然发现,小明惊呼:“WC,原。。。”
    游戏
    大家一定玩过论坛的抓小猫吧(404界面),没玩过的也没事,现在打开链接也能玩【此处感谢Ganlv佬提供的游戏】
    作为常在水区抓猫的“抓猫高手”,是时候展现真正的“寄”术了
    (emmm,确实有些汗流浃背)

    Hacker(破解游戏,调低难度)
    用7-zip打开apk文件,发现里面有抓猫猫的主程序catch-the-cat.js,

    这和论坛的抓猫游戏是一样诶,那就好办了
    我们直接参照Ganlv佬的代码
    修改一下抓猫猫的js主程序catch-the-cat.js,定位到变量initialWallCount
    把墙的数量改多一点,初始是10个所以抓不住,那么就把墙的数量改一下变成30吧(doge)

    然后替换js文件,因为文件被修改过了,所以apk需要重新签名一下
    这里采用Lucky Patcher,打个测试签名就行了,以下安卓部分修改后都用此方式打签名,不再重复赘述

    “这下是谁该汗流浃背了呢......”

    抓住猫,熟悉的bgm响起,看描述就猜到的标准结局......
    “Genshin,Start!”



    Flag:flag{happy_new_year_2024}

    Disassemble(反编译dex看函数)
    说完了上面的破解游戏本体,接下来就是直接对安装包本体下手了,上apktool
    解包发现了一个以作者名字命名的文件夹,
    里面有MainActivity和YSQDActivity(原神启动?)

    分析:
    [ol]

  • 主进程调用Webview运行抓猫游戏

  • 抓住猫以后播放ys.mp4

  • 视频播放完以后用SetText显示Flag

    [/ol]
    (最后的flag是通过字符操作得到的,没有直接给出,而在该文件中也没有提及具体操作步骤,猜想flag可能藏在那个播放的视频ys.mp4的文件末尾,因时间原因就不跟下去了)

    解题领红包之四 {Android 初级题}
    寄语:如果不会解题还想拿分那赶紧来现学现卖吧,只要认真看完并动手练习,肯定能解出来本题,吾爱破解安卓逆向入门教程《安卓逆向这档事》。
    游戏
    第二个小游戏,居然是抽卡

    (吐槽一句:这0.6%概率真有点低啊......)
    BUG玩法
    这个程序没有采用数据库方式,退出重新进入程序即刷新次数为10次
    于是乎,“只要我不停地抽,0.6%也不算什么”(doge)


    Flag:flag{52pj_HappyNewYear2024}

    Hacker(破解游戏,调低难度)
    用英文含义命名Activity是个好习惯,至少对于CM而言是这样
    用apktool反编译apk文件,在程序中,我们又发现了作者的信息

    各位是否发现增加一抽所需要的时间在不断递增?我们简单修改一下增加一抽所需要时间(修改概率、保底同理,只要修改对应的数值就行了)
    将array_1中的数据全部改成0x1(即增加一抽仅需要1s),最坏情况下也只需要90s

    修改、编译一气呵成(注意这时不要签名,因为flag在签名里面),直接核心破解安装即可,发现此时软件已经变成1秒增加1抽了(doge)

    解题领红包之五 {Android 中级题}
    题目简介:我,玄天帝,,解封!!!
    游戏
    emmm,九宫格图形解锁

    运用强大的搜索引擎,找到了一个类似的开源项目GestureLock
    小知识,安卓系统的图形是以数字密码形式存放在文件中的,所以推断该图形代表的也是一个数字字符串
    静态分析
    使用jde反编译得到一堆smali文件,打开以作者名字命名的文件夹
    GestureUnlock本体
    既然是游戏,那么我们就研究一下这款游戏的本体吧,找到以下定义:
    public GestureUnlock(Context context0, AttributeSet attributeSet0, int v) {
            super(context0, attributeSet0, v);
            this.cicleRadius = 10;
            this.firstInit = false;
            this.points = new ArrayList();
            this.selectP = new ArrayList();
            this.alreadyTouch = false;
            this.isUp = false;
            this.lockTouch = false;
            this.returnFun = 0;
            this.defaultKey = "01234";
            this.setUpKey = "";
            this.errorKey = "";
    }
    在MainActivity里面也没找到修改密码的Set函数,所以我们试试它的默认密码"01234"
    嗯,看上去是对了,然后啥都没发生(我的flag呢!
    【注意:此处为GestureUnlock(也就是这个密码锁)的密码,但不是Flag的密码,Flag的密码s在后续会说到】

    后续分析发现输入该密码时会进入函数isSuccessful,补充一句该函数是GestureUnlock自带密码正确的回调函数,但此题关键不在此处
    public void isSuccessful(String s) {
        Log.e("zj595", s);
    }
    而只有输入"错误"的密码才会进入真正的函数isError
    public void isError(String s) {
        Log.e("zj595", s);
        MainActivity.this.checkPassword(s); //checkPassword是个很重要的函数,接下来就会说到
    }
    解密函数
    如上部分所述,在MainActivity中提及了一个重要的函数:checkPassword,先看它的smali code:
    .method public checkPassword(String)Z
              .registers 11
    00000000  const/4             v0, 0
    :try_2
    00000002  invoke-virtual      MainActivity->getAssets()AssetManager, p0  #读取Assets中的classes.dex
    00000008  move-result-object  v1
    0000000A  const-string        v2, "classes.dex"
    0000000E  invoke-virtual      AssetManager->open(String)InputStream, v1, v2
    00000014  move-result-object  v1
    00000016  invoke-virtual      InputStream->available()I, v1
    0000001C  move-result         v2
    0000001E  new-array           v2, v2, [B
    00000022  invoke-virtual      InputStream->read([B)I, v1, v2
    00000028  new-instance        v3, File
    0000002C  const-string        v4, "data"
    00000030  invoke-virtual      MainActivity->getDir(String, I)File, p0, v4, v0
    00000036  move-result-object  v4
    00000038  const-string        v5, "1.dex"
    0000003C  invoke-direct       File->(File, String)V, v3, v4, v5
    00000042  new-instance        v4, FileOutputStream
    00000046  invoke-direct       FileOutputStream->(File)V, v4, v3
    0000004C  invoke-virtual      FileOutputStream->write([B)V, v4, v2  #将classes.dex释放至"/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex"
    00000052  invoke-virtual      FileOutputStream->close()V, v4
    00000058  invoke-virtual      InputStream->close()V, v1
    0000005E  const-string        v1, "dex"
    00000062  invoke-virtual      MainActivity->getDir(String, I)File, p0, v1, v0
    00000068  move-result-object  v1
    0000006A  invoke-virtual      Object->getClass()Class, p0
    00000070  move-result-object  v2
    00000072  invoke-virtual      Class->getClassLoader()ClassLoader, v2
    00000078  move-result-object  v2
    0000007A  new-instance        v4, DexClassLoader
    0000007E  invoke-virtual      File->getAbsolutePath()String, v3
    00000084  move-result-object  v3
    00000086  invoke-virtual      File->getAbsolutePath()String, v1
    0000008C  move-result-object  v1
    0000008E  const/4             v5, 0
    00000090  invoke-direct       DexClassLoader->(String, String, String, ClassLoader)V, v4, v3, v1, v5, v2
    00000096  const-string        v1, "com.zj.wuaipojie2024_2.C"
    0000009A  invoke-virtual      DexClassLoader->loadClass(String)Class, v4, v1  # 加载classes.dex里面的C Activity,和上面的C.smali对应
    000000A0  move-result-object  v1
    000000A2  const-string        v2, "isValidate"
    000000A6  const/4             v3, 3
    000000A8  new-array           v4, v3, [Class
    000000AC  const-class         v6, Context
    000000B0  aput-object         v6, v4, v0
    000000B4  const-class         v6, String
    000000B8  const/4             v7, 1
    000000BA  aput-object         v6, v4, v7
    000000BE  const-class         v6, [I
    000000C2  const/4             v8, 2
    000000C4  aput-object         v6, v4, v8
    000000C8  invoke-virtual      Class->getDeclaredMethod(String, [Class)Method, v1, v2, v4
    000000CE  move-result-object  v1
    000000D0  new-array           v2, v3, [Object
    000000D4  aput-object         p0, v2, v0
    000000D8  aput-object         p1, v2, v7
    000000DC  invoke-virtual      MainActivity->getResources()Resources, p0  # actual call site: Landroidx/appcompat/app/AppCompatActivity;->getResources()Landroid/content/res/Resources;
    000000E2  move-result-object  p1
    000000E4  sget                v3, R$array->A_offset:I
    000000E8  invoke-virtual      Resources->getIntArray(I)[I, p1, v3  # 传入Gesture构成的数字数组
    000000EE  move-result-object  p1
    000000F0  aput-object         p1, v2, v8
    000000F4  invoke-virtual      Method->invoke(Object, [Object)Object, v1, v5, v2
    000000FA  move-result-object  p1
    000000FC  check-cast          p1, String
    00000100  if-eqz              p1, :12E              # 如果比较结果等于0,则跳转12E
    :104
    00000104  const-string        v1, "唉!"
    00000108  invoke-virtual      String->startsWith(String)Z, p1, v1
    0000010E  move-result         v1
    00000110  if-eqz              v1, :12E
    :114
    00000114  iget-object         v1, p0, MainActivity->tvText:TextView
    00000118  invoke-virtual      TextView->setText(CharSequence)V, v1, p1
    0000011E  iget-object         p1, p0, MainActivity->myunlock:GestureUnlock
    00000122  const/16            v1, 8
    00000126  invoke-virtual      GestureUnlock->setVisibility(I)V, p1, v1
              .catch Exception {:try_2 .. :tryend_12C} :catch_130
    :tryend_12C
    0000012C  return              v7
    :12E
    0000012E  return              v0
    :catch_130  # used for: Ljava/lang/Exception;
    00000130  move-exception      p1
    00000132  invoke-virtual      Exception->printStackTrace()V, p1
    00000138  return              v0
    .end method
    反编译为java就是:
    public class MainActivity extends AppCompatActivity {
        private GestureUnlock myunlock;
        private TextView tvText;
        static {
            System.loadLibrary("52pj");
        }
        public boolean checkPassword(String s) {
            try {
                InputStream inputStream0 = this.getAssets().open("classes.dex");
                byte[] arr_b = new byte[inputStream0.available()];
                inputStream0.read(arr_b);
                File file0 = new File(this.getDir("data", 0), "1.dex");
                FileOutputStream fileOutputStream0 = new FileOutputStream(file0);
                fileOutputStream0.write(arr_b);
                fileOutputStream0.close();
                inputStream0.close();
                File file1 = this.getDir("dex", 0);
                ClassLoader classLoader0 = this.getClass().getClassLoader();
                String s1 = (String)new DexClassLoader(file0.getAbsolutePath(), file1.getAbsolutePath(), null, classLoader0).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[].class).invoke(null, this, s, this.getResources().getIntArray(array.A_offset));
                if(s1 != null && (s1.startsWith("唉!"))) {
                    this.tvText.setText(s1);
                    this.myunlock.setVisibility(8);
                    return true;
                }
            }
            catch(Exception exception0) {
                exception0.printStackTrace();
                return false;
            }
            return false;
        }
        @ Override  // androidx.fragment.app.FragmentActivity
        protected void onCreate(Bundle bundle0) {
            super.onCreate(bundle0);
            this.setContentView(layout.activity_main);
            TextView textView0 = (TextView)this.findViewById(id.tv_text);
            this.tvText = textView0;
            textView0.setText("  吾名玄天帝,昔为诸界之尊,因古诅咒,沉睡亿载。今幸苏醒,欲召百万神兵仙将,复掌万界,重铸天序。此举,需汝解封印,贡力之源。若助吾破诅归位,赐汝万界神尊,封一神域为土,永居众神之巅。");
            GestureUnlock gestureUnlock0 = (GestureUnlock)this.findViewById(id.myunlock);
            this.myunlock = gestureUnlock0;
            gestureUnlock0.setIGestureListener(new IGestureListener() {
                @ Override  // com.example.gesturelock.IGestureListener
                public void isError(String s) {
                    Log.e("zj595", s);
                    MainActivity.this.checkPassword(s);
                }
                @ Override  // com.example.gesturelock.IGestureListener
                public void isSetUp(String s) {
                }
                @ Override  // com.example.gesturelock.IGestureListener
                public void isSuccessful(String s) {
                    Log.e("zj595", s);
                }
            });
        }
    }
    程序逻辑还是比较明确的,复制一份classes.dex,命名为1.dex,动态加载dex文件中“com.zj.wuaipojie2024_2.C”类中的“isValidate”函数,传入图形密码字符串,读取返回字符串,如果返回字符串以“诶!”开头,则隐藏密码锁,展示该字符串。
    接下来分析这个“isValidate”函数,同样转为java方便观看
    public static String isValidate(Context context0, String s, int[] arr_v) throws Exception {
            try {
                return (String)C.getStaticMethod(context0, arr_v, "com.zj.wuaipojie2024_2.A", "d", new Class[]{Context.class, String.class}).invoke(null, context0, s);
            }
            catch(Exception exception0) {
                Log.e("ZJ595", "咦,似乎是坏掉的dex呢!");
                exception0.printStackTrace();
                return "";
            }
        }
    "isValidate"这个函数调用了同文件下的"getStaticMethod"函数
    private static Method getStaticMethod(Context context0, int[] arr_v, String s, String s1, Class[] arr_class) throws Exception {
            try {
                File file0 = C.fix(C.read(context0), arr_v[0], arr_v[1], arr_v[2], context0);
                ClassLoader classLoader0 = context0.getClass().getClassLoader();
                File file1 = context0.getDir("fixed", 0);
                Method method0 = new DexClassLoader(file0.getAbsolutePath(), file1.getAbsolutePath(), null, classLoader0).loadClass(s).getDeclaredMethod(s1, arr_class);
                file0.delete();
                new File(file1, file0.getName()).delete();
                return method0;
            }
            catch(Exception exception0) {
                exception0.printStackTrace();
                return null;
            }
        }
    "getStaticMethod""函数又调用了"fix"函数对dex文件进行修复,生成2.dex后加载修复后的com.zj.wuaipojie2024_2.A类中的d函数至method0,然后删除生成的2.dex文件后返回(此处为重点)
    注:修复前的A类是这样的
    public class A {
        private static final String SUCCESS_TAG = "唉!";
        public static boolean b() {
            return false;
        }
        public static String c(String s) {
            return "?" + s + "?";
        }
        public static String d(Context context0, String s) {
            return "?" + s + "?";
        }
    }
    "fix"函数以及与之对应的"read"函数是这样的
        private static File fix(ByteBuffer byteBuffer0, int v, int v1, int v2, Context context0) throws Exception {
            try {
                File file0 = context0.getDir("data", 0);
                int v3 = (int)(((Integer)D.getClassDefData(byteBuffer0, v).get("class_data_off")));
                HashMap hashMap0 = D.getClassData(byteBuffer0, v3);
                ((int[][])hashMap0.get("direct_methods"))[v1][2] = v2;
                byte[] arr_b = D.encodeClassData(hashMap0);
                byteBuffer0.position(v3);
                byteBuffer0.put(arr_b);
                byteBuffer0.position(0x20);
                byte[] arr_b1 = new byte[byteBuffer0.capacity() - 0x20];
                byteBuffer0.get(arr_b1);
                byte[] arr_b2 = Utils.getSha1(arr_b1);
                byteBuffer0.position(12);
                byteBuffer0.put(arr_b2);
                int v4 = Utils.checksum(byteBuffer0);
                byteBuffer0.position(8);
                byteBuffer0.putInt(Integer.reverseBytes(v4));
                byte[] arr_b3 = byteBuffer0.array();
                File file1 = new File(file0, "2.dex");
                FileOutputStream fileOutputStream0 = new FileOutputStream(file1);
                fileOutputStream0.write(arr_b3);
                fileOutputStream0.close();
                return file1;
            }
            catch(Exception exception0) {
                exception0.printStackTrace();
                return null;
            }
        }
        private static ByteBuffer read(Context context0) {
            try {
                File file0 = new File(context0.getDir("data", 0), "decode.dex");
                if(!file0.exists()) {
                    return null;
                }
                FileInputStream fileInputStream0 = new FileInputStream(file0);
                byte[] arr_b = new byte[fileInputStream0.available()];
                fileInputStream0.read(arr_b);
                ByteBuffer byteBuffer0 = ByteBuffer.wrap(arr_b);
                fileInputStream0.close();
                return byteBuffer0;
            }
            catch(Exception unused_ex) {
                return null;
            }
        }
    看看chatgpt对以上代码的分析:
    这段代码是一个Android应用中的一些Java代码,主要涉及到动态加载Dex文件的操作。下面我来逐个分析这几个函数的作用:
    [ol]

  • fix(ByteBuffer byteBuffer0, int v, int v1, int v2, Context context0)
  • 这个函数的作用是修复一个ByteBuffer中的Dex文件,其中包括了修改Dex文件中的一些数据。
  • 参数:
  • ByteBuffer byteBuffer0:包含Dex文件内容的缓冲区。
  • int v, int v1, int v2:用于指定要修改的数据的位置和值。
  • Context context0:上下文对象,用于获取应用程序的目录等信息。
  • 主要步骤:
    [ol]
  • 解析Dex文件中的类数据和方法数据。
  • 根据传入的参数修改方法数据中的某个值。
  • 将修改后的数据写回ByteBuffer中。
  • 计算并更新Dex文件的SHA1校验和和校验码。
  • 将修改后的ByteBuffer写入到应用程序的私有目录中,并返回该文件。
    [/ol]

  • getStaticMethod(Context context0, int[] arr_v, String s, String s1, Class[] arr_class)
  • 这个函数的作用是获取一个静态方法。
  • 参数:
  • Context context0:上下文对象。
  • int[] arr_v:一个整型数组,用于指定修复Dex文件时所需的参数。
  • String s, String s1:要调用的类名和方法名。
  • Class[] arr_class:方法参数的类型数组。
  • 主要步骤:
    [ol]
  • 调用fix()函数修复Dex文件,并获取修复后的文件。
  • 使用修复后的Dex文件创建一个DexClassLoader。
  • 加载指定类名的类,并获取其中声明的静态方法。
  • 删除修复后的文件和其在私有目录中的引用。
  • 返回获取到的静态方法。
    [/ol]

  • isValidate(Context context0, String s, int[] arr_v)
  • 这个函数的作用是验证某个字符串。
  • 参数:
  • Context context0:上下文对象。
  • String s:要验证的字符串。
  • int[] arr_v:一个整型数组,用于指定修复Dex文件时所需的参数。
  • 返回值:返回验证结果的字符串。
  • 主要步骤:
    [ol]
  • 调用getStaticMethod()函数获取指定类的静态方法。
  • 调用获取到的静态方法来进行验证,并返回结果。
    [/ol]

  • read(Context context0)
  • 这个函数的作用是读取Dex文件。
  • 参数:
  • Context context0:上下文对象。
  • 返回值:返回读取到的Dex文件的ByteBuffer。
  • 主要步骤:
    [ol]
  • 构造Dex文件的路径。
  • 读取Dex文件内容到一个字节数组中。
  • 将字节数组包装成ByteBuffer,并返回。
    [/ol]

    [/ol]
    这些函数主要用于动态加载并修复Dex文件,然后执行其中的方法来实现某些功能。
    思路
    目前flag的关键在于两点,一个是图形锁代表的字符串s,一个是修复dex以后两个"?"的内容,
    动态分析&dex修复*3
    修复1
    静态分析结束,下面用010Editor打开classes.dex,图中可以明显看到dex的Adler32校验值和sha1校验值是错误的
    也就是说附件里的classes.dex文件是损坏的
    dex校验值损坏后,在反射加载类过程中会被系统拒绝,因此必须进行修复操作
    读取logcat可见如下错误

    E System : Unable to Load dex file: /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex
    E System : java.io.IOException: Failed to open dex files : from /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex because: Failure to verify dex file ' /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex': Bad Checksum (c607ea12, expected 22dcea4c)

    可见系统确实因校验值异常,拒绝加载了assets下复制的classes.dex

    由于dex文件在010Editor中除了头部外并无明显异常情况,因此我们使用DexRepair对dex头部进行修复:java -jar DexRepair.jar /path/to/dex
    修复后头部变为全绿:

    将修复完的dex命名为classes.dex,置入assets文件夹,重新打包、签名
    打开jeb调试,发现依然无法正常运行
    修复2
    单步调试,定位到错误发生点,发现是程序在
    try {
        File file0 = new File(context0.getDir("data", 0), "decode.dex");
        if(!file0.exists()) {
            return null;
        }
    }
    处返回了null,即这个file0,也就是decode.dex不存在
    然后程序跳转到了
    catch(Exception exception0) {
        Log.e("ZJ595", "咦,似乎是坏掉的dex呢!");
        exception0.printStackTrace();
        return "";
    }
    打开adb logcat工具可见返回的错误信息:

    E ZJ595 咦,似乎是坏掉的dex呢!

    可见确实是文件不存在的原因导致程序运行异常
    不存在我们就手动新建一个,为了方便起见我就直接复制了一份1.dex,将其改名为decode.dex,使用RE文件管理器放置于与1.dex同目录下(即data/user/0/com.zj.wuaipojie2024_2/app_data),这样这部分代码就能正常跑起来了
    根据"静态分析"部分代码分析,由于2.dex文件在写入、加载后即会进行自我删除,于是需要在删除代码位置下断点以保存该中间文件进行分析,断点位置如下:
    .method private static varargs getStaticMethod(Context, [I, String, String, [Class)Method
              .registers 11
              .annotation system Signature
                  value = {
                      "(",
                      "Landroid/content/Context;",
                      "[I",
                      "Ljava/lang/String;",
                      "Ljava/lang/String;",
                      "[",
                      "Ljava/lang/Class;)",
                      "Ljava/lang/reflect/Method;"
                  }
              .end annotation
              .annotation system Throws
                  value = {
                      Exception
                  }
              .end annotation
    00000000  const/4             v0, 0
    :try_2
    00000002  invoke-static       C->read(Context)ByteBuffer, p0
    00000008  move-result-object  v1
    0000000A  const/4             v2, 0
    0000000C  aget                v3, p1, v2
    00000010  const/4             v4, 1
    00000012  aget                v4, p1, v4
    00000016  const/4             v5, 2
    00000018  aget                p1, p1, v5
    0000001C  invoke-static       C->fix(ByteBuffer, I, I, I, Context)File, v1, v3, v4, p1, p0
    00000022  move-result-object  p1
    00000024  invoke-virtual      Object->getClass()Class, p0
    0000002A  move-result-object  v1
    0000002C  invoke-virtual      Class->getClassLoader()ClassLoader, v1
    00000032  move-result-object  v1
    00000034  const-string        v3, "fixed"
    00000038  invoke-virtual      Context->getDir(String, I)File, p0, v3, v2
    0000003E  move-result-object  p0
    00000040  new-instance        v2, DexClassLoader
    00000044  invoke-virtual      File->getAbsolutePath()String, p1
    0000004A  move-result-object  v3
    0000004C  invoke-virtual      File->getAbsolutePath()String, p0
    00000052  move-result-object  v4
    00000054  invoke-direct       DexClassLoader->(String, String, String, ClassLoader)V, v2, v3, v4, v0, v1
    0000005A  invoke-virtual      DexClassLoader->loadClass(String)Class, v2, p2
    00000060  move-result-object  p2
    00000062  invoke-virtual      Class->getDeclaredMethod(String, [Class)Method, p2, p3, p4
    00000068  move-result-object  p2
    0000006A  invoke-virtual      File->delete()Z, p1  #在此处下断点,防止文件删除
    00000070  new-instance        p3, File
    00000074  invoke-virtual      File->getName()String, p1
    0000007A  move-result-object  p1
    0000007C  invoke-direct       File->(File, String)V, p3, p0, p1
    00000082  invoke-virtual      File->delete()Z, p3
              .catch Exception {:try_2 .. :tryend_88} :catch_8A
    :tryend_88
    00000088  return-object       p2
    :catch_8A  # used for: Ljava/lang/Exception;
    0000008A  move-exception      p0
    0000008C  invoke-virtual      Exception->printStackTrace()V, p0
    00000092  return-object       v0
    .end method
    然后在data/user/0/com.zj.wuaipojie2024_2/app_data下即可找到修复后的2.dex文件
    修复后的2.dex中的A类如下:
    package com.zj.wuaipojie2024_2;
    import android.content.Context;
    public class A {
        private static final String SUCCESS_TAG = "唉!";
        public static boolean b() {
            return false;
        }
        public static String c(String s) {
            return "?" + s + "?";
        }
        public static String d(Context context0, String s) {
            MainActivity.sSS(s);
            String s1 = Utils.getSignInfo(context0);
            if(s1 != null && (s1.equals("fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117"))) {
                StringBuffer stringBuffer0 = new StringBuffer();
                for(int v = 0; stringBuffer0.length()
    此处的A.d函数给出了密码s的真值以及一个提示
    写个小程序获取解锁密码s:
    import java.io.*;
    public class GetPass {
        public static void main(String[] args) {
            StringBuffer stringBuffer0 = new StringBuffer();
            for(int v = 0; stringBuffer0.length()
    输出为:

    048531267 (九宫格密码的真值,后续会用到)


    下面来看看提示:

    "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d"

    找到B类......
    (怎么还是讨厌的"?"+s+"?")
    public class B {
        public static String d(String s) {
            return "?" + s + "?";
        }
    }
    看来这个B.d还是坏的,还得修复一次
    修复3
    原先程序里的那个fix函数只能修复A.d部分,接下来修复B.d
    回到MainActivity,还是定位到这句
    String s1 = (String)new DexClassLoader(file0.getAbsolutePath(), file1.getAbsolutePath(), null, classLoader0).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[].class).invoke(null, this, s, this.getResources().getIntArray(array.A_offset));
    语句中出现了A_offset,说明是修复A类型的,定位到array.A_offset这个变量
    public static final class array {
            public static int A_offset = 0x7F030000;  // array:A_offset
            public static int B_offset = 0x7F030001;  // array:B_offset
        }
    (诶,A_offset下面那个B_offset是啥?这不是我们要找的“B类”的偏移嘛,这下不用手算了)
    直接jeb跑起来,在A_offeset处下断点,把A_offset的值换成B_offset
    000000E4  sget                v3, R$array->A_offset:I  #此处下断点,修改Locals中v3的值为B_offset,即将其改为7F30001h,如图
    000000E8  invoke-virtual      Resources->getIntArray(I)[I, p1, v3

    运行程序,断点至本文"修复2"部分提及的删除函数,得到B.d修复后的2_new.dex(此处为了避免与上部分中2.dex混淆,将其命名为2_new.dex,实际由程序生成的文件名仍为2.dex)
    打开2_new.dex,反编译得到其中的B类,定位至B.d
    public class B {
        public static String d(String s) {
            return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
        }
    }

    Flag:"{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}"
    其中的password上面已经提到了,是"048531267"

    到此,这个CM终于完事了
    此处特别感谢 正己 佬提供的题目以及指点
    附——Utils中的两个算法(由smali代码反编译得到,无法直接运行):
        public static byte[] getSha1(byte[] arr_b) {
            try {
                return MessageDigest.getInstance("SHA").digest(arr_b);
            }
            catch(Exception unused_ex) {
                return null;
            }
        }
        public static String md5(byte[] arr_b) {
            int v;
            try {
                String s = new BigInteger(1, MessageDigest.getInstance("md5").digest(arr_b)).toString(16);
                v = 0;
                while(true) {
                label_2:
                    if(v >= 0x20 - s.length()) {
                        return s;
                    }
                    s = "0" + s;
                    break;
                }
            }
            catch(NoSuchAlgorithmException unused_ex) {
                throw new RuntimeException("ops!!");
            }
            ++v;
            goto label_2;
        }
    写个小程序计算一下flag(注册机)
    import java.io.*;
    import java.math.BigInteger;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Scanner;
    public class GetFlag {
        public static byte[] getSha1(byte[] arr_b) {
            try {
                return MessageDigest.getInstance("SHA").digest(arr_b);
            }
            catch(Exception unused_ex) {
                return null;
            }
        }
        public static String md5(byte[] arr_b) {
            try {
                String s = new BigInteger(1, MessageDigest.getInstance("md5").digest(arr_b)).toString(16);
                return s;
            }
            catch(NoSuchAlgorithmException unused_ex) {
                throw new RuntimeException("ops!!");
            }
        }
        public static void main(String args[]) {
            try (Scanner scanner = new Scanner(System.in)) {
                System.out.println("Please Input Your UID:");
                String s = "048531267" + scanner.next();
                System.out.println("Flag:" + "{" + GetFlag.md5(GetFlag.getSha1(s.getBytes())) + "}" );
            }
        }
    }

    总结
    正如开头所说,这是我第一次参与红包活动,也是第一次接触安卓类的逆向
    因文章篇幅所限,一些细节的内容(比如环境配置、设置apk为可调试等)不能详尽地涉及,如果有对以上部分有疑问和建议也欢迎各位在楼下交流讨论
    总体而言这几道题目相对来说难度还行(主要还是我太菜了)
    Web部分
    前言
    好家伙,这下Web题越来越像猜灯谜了,而且还是眼睛有点酸的那种......
    (说的就是flag1和3,愿称之为opencv独家赞助伙伴【bushi】)
    题目简介
    活动地址:https://www.52pojie.cn/thread-1889163-1-1.html
    解题线索视频:https://www.bilibili.com/video/BV1ap421R7VS
    题目共包含 12 个静态 flag: flag1~flag12,另外还需要寻找到 3 个动态 flag: flagA~flagC,每个难度需提交对应的4个静态flag和1个动态flag
    准备工作
    本次的视频隐藏的信息有点复杂,有两个flag都需要逐帧分析,因此我们先用ffmpeg工具把视频转换为图帧
    (我没有阿B的大会员,下载的视频是30fps的,因此r的参数是30,若下载的是60fps的,只要调整以下命令中r的参数为60就行了)
    ffmpeg -i inputfile.mp4 -r 30 ./images/%1d.jpg
    得到一文件夹的图片:

    接下来就是处理那个切成了四段的二维码,
    认真看的小伙伴应该已经发现了,这个二维码的几个帧拼在一起就是一个完整的二维码
    使用修图软件框选太麻烦了,有的二维码部分甚至和文字的白色部分连在了一起,
    既然我对cv领域比较熟悉,因此我还是用opencv叠加吧
    下面是一个用python写的叠加小程序:
    import cv2
    import os
    def add_images_in_folder(folder_path):
        # Get a list of all files in the folder
        image_files = [f for f in os.listdir(folder_path) if f.endswith('.jpg')]
        if len(image_files) == 0:
            print("No JPG images found in the folder.")
            return None
        # Read the first image to initialize the accumulator
        result = cv2.imread(os.path.join(folder_path, image_files[0]))
        # Loop through the rest of the images and add them to the accumulator
        for image_file in image_files[1:]:
            image_path = os.path.join(folder_path, image_file)
            image = cv2.imread(image_path)
            result = cv2.add(result, image)
        return result
    folder_path = "./images"
    result_image = add_images_in_folder(folder_path)
    if result_image is not None:
        cv2.imwrite("./result.png",result_image)
    手动处理去除文字部分,得到完整的二维码如图:

    解码得到网址:https://2024challenge.52pojie.cn/
    Flag1
    视频中出现52pojie四个字的时候,后面点阵散开处点阵缺少了一些点,而这些缺少的点就隐藏了flag1,我们用准备部分提到的“叠加小程序”对此部分帧图片进行叠加,得到可见flag
    (这个flag也可以直接看,就是有点费眼【doge】)
    拼接结果:


    flag1{52pj2024}

    Flag2
    参考去年的官方题解中flag2部分解释:

    因为页面会自动重定向,我本来想将 X-Dynamic-Flag: flagA{Header X-52PoJie-Uid Not Found} 藏在这个重定向之前的页面的,但是我怕藏得太深了,没这么搞。

    今年的flag2果然藏在了重定向页面前......
    访问上述二维码指向的的网页会产生重定向,打开F12打开控制台后重新访问该链接即可看到X-Flag2:


    flag2{xHOpRP}

    Flag3
    视频开头那段东西就是,一看到就想到了这个视频,
    二值杂色视觉暂留效应嘛,“二值杂色”+“视觉暂留”。
    前者是指把一个复杂的图像,按照灰度不同去四舍五入为黑与白两种噪点。后者则意味着需要借助你眼睛与脑子的时间差,去串联起前后的噪点位置的变化,让你的脑子中形成轨迹图片。
    这玩意常用在验证码上,用来过AI的,所以只能人工看了。

    flag3{GRsgk2}

    Flag4
    打开上述提到的网址,F12,查看网络,会发现有一个文件名叫做flag4_flag10.png的空白图片作为背景(实际上是透明的,浏览器看不出而已)
    body {
                margin: 0;
                padding: 0;
                background: url("flag4_flag10.png") white center center no-repeat;
                background-size: contain;
                height: 100vh;
                overflow: hidden;
            }

    下载下来使用图片应用打开即可看到flag4;


    flag4{YvJZNS}

    FlagA
    同样是开上述提到的网址,F12,查看网络,输入uid登录,会发现有一个叫做login的网页写入了cookie信息
    而cookie信息中提到了flagA

    flagA=guFOgjwXg5haqETMpDMLPyHfY7sP5sf32rW7l3XtVr+9T+LyBKQhmslLNA==; expires=Sat, 17 Feb 2024 06:00:00 GMT; path=/; SameSite=Lax
    看起来flagA被加密了,而且不是base64
    但与此同时,uid好像也采用了类似的加密方式,而且网址里好像有个script API用来把cookie转换为uid(嗯?)
            fetch('/auth/uid').then(res => res.text()).then(res => {
                if (res) {
                    document.querySelector('#uid').textContent = res;
                    document.querySelector('#logout-form').style.display = '';
                    document.querySelector('#login-form').style.display = 'none';
                }
            });
    那么就好办了,我们来偷梁换柱一下,把cookie中的flagA设置到uid中,再fetch......


    flagA{f96a1e5e}

    注:flagA每10分钟刷新
    Flag5
    还是那个网页,F12,网页里用注释提到了Flag5

    我们把style中的属性全部去掉,得到以下一串东西(用图片了,直接发论坛MD渲染会崩):

    我们暂且不管. _ / \那一堆符号(flag9的地方会说),剩下的就是flag5

    flag5{P3prqF}

    Flag6
    还是那个网页,下方有个flag6的按钮,点击进入flag6
    网页很干净,就一个按钮,点了就开始炼丹,电脑风扇呼呼响(doge)

    还是来看看源码吧:
    document.querySelector("button").addEventListener("click", () => {
      const t0 = Date.now();
      for (let i = 0; i
    简而言之就是它跑了一个从0到$10^8 - 1$的数字字符串i,当该字符串的md5为
    [color=]1c450bbafad15ad87c32831fa1a616fc
    时,输出flag6{${i}},否则在console中定期输出计算进度
    (好家伙,暴力破解md5,真有你的)
    直接md5彩虹表反查,发现是今天的日期,绝了......


    flag6{20240217}

    Flag7
    作者在视频里面留下了一个Github网址,打开发现这个:


    "删除不小心提交的flag内容"

    提示够明显了,我们直接点击commit寻找历史提交记录,找到了这个


    flag7{Djl9NQ}

    Flag8 & FlagB
    2048小游戏

    首先肯定是玩咯,轻轻松松通过玩游戏顺利拿到 flag8


    flag8{OaOjIK}

    接着拿剩下的金币V了作者50(doge)

    竟然真的有人v我50,真的太感动了。作为奖励呢,我就提示你一下吧,关键词是“溢出”。

    首先想到的肯定是多买点,然后让它溢出,可惜有可能弄得太猛了,导致溢出后金币数量增加了,作者也想到了这个,购买请求直接被拦截了

    猜想是做了检验,即购买后金币数量不能高于现有数量。
    手算了几个临界值,罢了,完全没用,
    因为题目没有写明白它后端到底用的啥数据类型,因此放弃思考,直接用request组件爆破
    # 导入requests模块
    import requests
    for i in [2 ** j for j in range(2,64)]:
        print(i)
        # 请求的url地址
        url = 'https://2024challenge.52pojie.cn/flagB/buy_item'
        # 请求头
        headers = {"content-type":"application/x-www-form-urlencoded","cookie":"Hm_lvt_46d556462595ed05e05f009cdafff31a=1707280828,1707352290,1707440981,1708065094; wzws_sessionid=gmY5MmRiY4AxODMuMTkzLjE1My4yMjCBMTNmOWIzoGXQNBg=; guFOgjwXg5haqETMpDMLPyHfY7sP5sf32rW7l3XtVr+9T+LyBKQhmslLNA==; uid=BTtCuUGDQGSkBsn/UatmT1VT4wNkVf1j4O5UsVxg9yguZA==; game2048_user_data=I1xnNzcQVLZgwF2jXweH+0MFEE3RglZSqpAhElrNkr5VWSjGb885YMYIqMyGAZJGqCFvZ1oCV50LnAJbBvQuPLM0deHxcni4v3dvVKohNEaWNui6WbpPusQ2ff13MWv7wkO1jX/cfa0fZQOJK7UtfQvrUlJD+1GqDCYs7TCYLLEtrObxDt74D2Jswg4ViV9/1o5HHtDI"}
        # payload 为传入的参数
        payload = {"shop_item_id":5,"buy_count":i}
        # json形式,参数用json
        res = requests.post(url,data=payload,headers=headers)
        print(res.text)
    运气还不错,跑到2^62 = 4611686018427387904时返回值为{"code":0,"msg":"OK"},也就是说买4611686018427387904个flagB时符合要求
    (其他的要不返回{"code":1,"msg":"购买商品之后钱怎么还变多了?不知道出什么 bug 了,暂时先拦一下 ^_^"},要不返回{"code":1,"msg":"钱不够"}
    后来尝试发现此时并未扣除任何金币,猜测此时乘上任何单价都会溢出,溢出后花销值变为0


    flagB{2a3ec954} 过期时间: 2024-02-17 12:10:00

    Flag9
    之前说到的那一串符号,调节窗口大小,即可看到立体的Flag9


    flag9{KHTALK}

    Flag10
    和Flag4是一张图片,图片misc类嘛,首先用binwalk看看是不是有什么不对劲的地方。
    好像还挺正常的,没有隐藏压缩包也没有藏图

    小插曲,这边diss一波edge浏览器,打开图片链接他会自动给你跳到它这个“边缘图像查看器”,即使你选择其中的“另存为”功能保存图片,下载到的图片也是被处理过的,隐写数据就丢了!!
    (“强大”???)

    所以,做这题时
    [color=]千 万 不 要
    用edge下载图片!!


    发现这个问题以后,直接用curl命令下载图片curl -l https://2024challenge.52pojie.cn/flag4_flag10.png -o ./flag4_flag10.png
    接下来就简单了,用stegsolve工具中的Analyse-Stereogram Solver(立体视图)工具,设置偏移量为1或2即可看到隐藏的flag10
    上图是curl下载图片(含隐写信息);下图是edge下载图片(无隐写信息)



    flag10{6BxMkW}

    Flag11
    拼图游戏,修改html:root中的css属性--var1和--var2,复原图片即可【两个变量的值分别为71和20】
    (小技巧,使用鼠标滚轮滚动参数,使图片块往聚合方向运动就没啥问题了)


    flag11{HPQfVF}

    Flag12
    WebAssembly(Wasm)技术,直接看关键部分
    (func $get_flag12 (;0;) (export "get_flag12") (param $var0 i32) (result i32)
        i32.const 1213159497
        i32.const 0
        local.get $var0
        i32.const 1103515245
        i32.mul
        i32.const 1
        i32.eq
        select
      )
    用chatgpt解释一下代码:
    这段WebAssembly(Wasm)代码定义了一个名为get_flag12的函数,该函数接受一个32位整数作为参数$var0,并返回一个32位整数作为结果。这个函数的作用是检查传入的参数是否与特定值相关联,如果是则返回1,否则返回0。
    具体而言,代码执行以下步骤:
    [ol]
  • i32.const 1213159497:将值1213159497(0x483CEEE9)压入堆栈。
  • i32.const 0:将值0压入堆栈。
  • local.get \$var0:将函数参数\$var0的值压入堆栈。
  • i32.const 1103515245:将值1103515245(0x41C64E6D)压入堆栈。
  • i32.mul:将栈顶两个值相乘。
  • i32.const 1:将值1压入堆栈。
  • i32.eq:比较栈顶两个值是否相等,如果相等则将1压入堆栈,否则将0压入堆栈。
  • select:根据栈顶的布尔值选择两个值中的一个放回栈顶。
    [/ol]
    因此,这段代码的主要目的是将参数$var0与特定的数值进行处理,并根据结果返回1或0。
    又是溢出问题,即1103515245*输入值 = 1,那么这个输入值一定很大(超出上限),
    这边提到了i32,即32位整型数据,最大值为2^31-1 = 2147483647
    写个C语言程序跑一下
    #include
    #include
    using namespace std;
    int main()
    {
        for(long j = 0; j
    嗯?结果跑出来个负数?不管了,填进去。

    -289805467

    这也能出答案是我没想到的......看来是满足条件就能出......


    flag12{HOXI}

    FlagC
    好家伙,直接内嵌TF.js跑Yolo目标检测模型了(不过看起来好像是yolov5n,没上v8不是很认可【doge】)......
    当小游戏直接玩吧,它都直接告诉你那些正确了,调整一下位置即可。

    我搭出的阵:

    下面来简单看一下这个网页的逻辑:
    fetch('/flagC/verify', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            },
                            body: JSON.stringify({
                                boxes,
                                scores,
                                classes,
                            }),
                            credentials: "include",
                        }).then((res) => res.json()).then((res) => {
                            const {hint, labels, colors} = res;
                            document.querySelector('#result').textContent = hint; // 错误时显示提示,正确时显示 flag
                        })
    简而言之就是调用TF.js(Tensorflow,Google开发的一款深度学习框架)加载yolo目标检测模型,对上传的图片执行本地目标检测
    然后将检测结果(boxes->【目标框】,scores【置信度】,classes【分类标签】)以POST形式传递至后端,接受后端返回的提示,并在图中框出来
    emmm,这个玩玩就好了,修改也没啥意思,因为判断逻辑在后端,程序能帮你做的也只能是根据提示不断修改,意义不大(doge)

    flagC{ce92f978} 过期时间: 2024-02-18 10:10:00

    函数, 文件

  • debug_cat   

    cattie牛批!
    debug_cat   


    PRETEXT 发表于 2024-2-25 21:00
    有什么高级工具,大佬推荐下

    看我课程里的
    长得帅活得久   

    多谢大佬的分析教程
    zouzhiqiang   

    大佬带带安卓高级的
    长得帅活得久   


    debug_cat 发表于 2024-2-25 09:17
    大佬带带安卓高级的
    "

    最主要后来没时间分析了有空一定玩玩
    @正己
    长得帅活得久   


    cattie 发表于 2024-2-25 09:27
    最主要后来没时间分析了有空一定玩玩

    高级题没头绪,头大
    debug_cat   

    "

    大佬,直接核心破解安装是咋弄的
    debug_cat   


    长得帅活得久 发表于 2024-2-25 12:32
    大佬,直接核心破解安装是咋弄的

    lucky patcher里面去除签名校验,
    然后系统安装相应的核心破解模块(lsp,禁用安卓的签名检验)
    参考这个:https://zhuanlan.zhihu.com/p/654235511
    长得帅活得久   

    两个Android初级题,简单看了一下代码,发现一个更bug的
    初级题一:直接解压apk,找到ys.mp4文件,放入Winhex中查看,直接滑到最下面,就是flag
    初级题二:用objection ,查看当前Activity列表,发现有个FlagActivity,直接启动flagActivity就行了
    您需要登录后才可以回帖 登录 | 立即注册