cattie 发表于 2024-2-25 07:52

吾爱破解2024红包题部分WriteUP

本帖最后由 cattie 于 2024-2-27 16:36 编辑


操作日志:24.2.27:合并Web题解

# Windows与安卓CM部分
## 前言
**第一年参加红包题目,其中有些操作可能不是那么熟练,感谢各位大佬批评指正,嗯。**
(后面的高级题太菜了不会做)
## 解题领红包之二 {Windows 初级题}
**新手题:送分题完成啦来试试新手题吧,点击下方“立即申请”任务,即可获得本题Windows CrackMe题目下载地址,通过分析CrackMe获得本题正确口令的解题方法。**
### 查壳
嗯,很好,没有壳,直接分析。
![](https://imgsrc.baidu.com/forum/pic/item/500fd9f9d72a60599f9684236e34349b033bba01.png)
### IDA静态分析
(这是什么鬼啊,撤了撤了)
**后来发现`ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$?`这一串东西是凯撒密码加密过的密文,偏移量为3,但字典是自定义的,懒得跟踪了,直接用OD了**
```cpp
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; // BYREF
unsigned int v17; //
void *Block; // BYREF
unsigned int v19; //
void *v20; // BYREF
int v21; //
unsigned int v22; //
int v23; //

Src = 0;
v17 = 15;
LOBYTE(Src) = 0;
sub_402560(Src, "ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$?", 0x24u);
v23 = 0;
SetConsoleTitleA(&ConsoleTitle);
v21 = 0;
v22 = 15;
LOBYTE(v20) = 0;
LOBYTE(v23) = 1;
v3 = sub_4027D0();
sub_402A80(v3);
sub_4031E0(&dword_42E088, v20);
v4 = v22;
v5 = (void **)v20;
if ( v21 == 36 )
{
    sub_402490(Src);
    sub_401FE0();
    LOBYTE(v23) = 2;
    v7 = Block;
    v8 = v20;
    if ( v19 >= 0x10 )
      v7 = (void **)Block;
    if ( v4 >= 0x10 )
      v8 = v5;
    if ( Block == (void *)36 )
    {
      v9 = 32;
      do
      {
      if ( *v8 != *v7 )
          break;
      ++v8;
      ++v7;
      v10 = v9 < 4;
      v9 -= 4;
      }
      while ( !v10 );
    }
    v11 = sub_4027D0();
    sub_402A80(v11);
    if ( v19 >= 0x10 )
    {
      v12 = Block;
      if ( v19 + 1 >= 0x1000 )
      {
      v12 = (void *)*((_DWORD *)Block - 1);
      if ( (unsigned int)(Block - 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;
    if ( v17 + 1 >= 0x1000 )
    {
      v14 = (void *)*((_DWORD *)Src - 1);
      if ( (unsigned int)(Src - v14 - 4) > 0x1F )
      _invalid_parameter_noinfo_noreturn();
    }
    sub_406010(v14);
}
return 0;
}
```
### OD动态调试
1. 打开OD,导入文件
2. 使用中文搜索引擎,找到一个Success和两个Try again,以及一个Tip
![](https://imgsrc.baidu.com/forum/pic/item/b03533fa828ba61eb313ad160734970a304e590d.png)
(Caesar Cipher恺撒密码......怪不得IDA看不懂)
3. 先看看第一个Try again
上方有个cmp和je,也就是说比较当前字符串长度是否长为36,如果是的话就继续判断,否则就输出 "Error, please try again"
![](https://imgsrc.baidu.com/forum/pic/item/3812b31bb051f819f3d366739cb44aed2e73e711.png)
我们输入36个1,输出变为了"Wrong, please try again",证明上述分析没有问题
![](https://imgsrc.baidu.com/forum/pic/item/d1a20cf431adcbefabdb1b3feaaf2edda3cc9f18.png)
4. 找到"Wrong, please try again"所在位置,找到判断条件,下断点,再次输入36个1,在MMX中即可找到CM的flag
![](https://imgsrc.baidu.com/forum/pic/item/9c16fdfaaf51f3de4f57dc5dd2eef01f3a297926.png)

> **Flag:fl@g{H@ppy\_N3w\_e@r!2o24!Fighting!!!}**


![](https://imgsrc.baidu.com/forum/pic/item/a50f4bfbfbedab645d9128f9b136afc379311e23.png)

## 解题领红包之三 {Android 初级题}
**题目简介:小明和李华是同学,最近小明发现李华技术进步很快,他太想进步了,于是他一直在观察李华,却发现他老是在玩圈小猫,直到一次偶然发现,小明惊呼:“WC,原。。。”**
### 游戏
大家一定玩过论坛的抓小猫吧(404界面),没玩过的也没事,现在[打开链接](https://www.52pojie.cn/1)也能玩【此处感谢Ganlv佬提供的游戏】
作为常在水区抓猫的“抓猫高手”,是时候展现真正的“寄”术了
(emmm,确实有些汗流浃背)
![](https://imgsrc.baidu.com/forum/pic/item/e61190ef76c6a7ef27d2c01fbbfaaf51f3de6617.png)
### Hacker(破解游戏,调低难度)
**用7-zip打开apk文件,发现里面有抓猫猫的主程序`catch-the-cat.js`,**
![](https://imgsrc.baidu.com/forum/pic/item/9a504fc2d5628535031bbc18d6ef76c6a7ef6314.png)
这和论坛的抓猫游戏是一样诶,那就好办了
我们直接参照Ganlv佬的[代码](https://github.com/ganlvtech/phaser-catch-the-cat)
修改一下抓猫猫的js主程序`catch-the-cat.js`,定位到变量`initialWallCount`
把墙的数量改多一点,初始是10个所以抓不住,那么就把墙的数量改一下变成30吧(doge)
![](https://imgsrc.baidu.com/forum/pic/item/21a4462309f7905201062c074af3d7ca7bcbd5ea.png)

然后替换js文件,因为文件被修改过了,所以apk需要重新签名一下
这里采用(https://www.luckypatchers.com/download/),打个测试签名就行了,以下安卓部分修改后都用此方式打签名,不再重复赘述
![](https://imgsrc.baidu.com/forum/pic/item/0b46f21fbe096b63540d35694a338744ebf8acf7.png)

**“这下是谁该汗流浃背了呢......”**
![](https://imgsrc.baidu.com/forum/pic/item/a8014c086e061d95764b5c013df40ad162d9cafe.png)

抓住猫,熟悉的bgm响起,看描述就猜到的标准结局......
“Genshin,Start!”
![](https://imgsrc.baidu.com/forum/pic/item/d31b0ef41bd5ad6eeff10fd3c7cb39dbb6fd3cfa.png)

![](https://imgsrc.baidu.com/forum/pic/item/6d81800a19d8bc3eb8616df3c48ba61ea8d34599.png)
> **Flag:flag{happy_new_year_2024}**

### Disassemble(反编译dex看函数)
说完了上面的破解游戏本体,接下来就是直接对安装包本体下手了,上apktool
解包发现了一个以作者名字命名的文件夹,
里面有MainActivity和YSQDActivity(原神启动?)
![](https://imgsrc.baidu.com/forum/pic/item/9358d109b3de9c82d2c992b62a81800a19d8435e.png)

分析:
1. 主进程调用Webview运行抓猫游戏
![](https://imgsrc.baidu.com/forum/pic/item/2e2eb9389b504fc234d0db3ca3dde71190ef6d78.png)

2. 抓住猫以后播放ys.mp4
![](https://imgsrc.baidu.com/forum/pic/item/d4628535e5dde711755528cfe1efce1b9d166100.png)

3. 视频播放完以后用SetText显示Flag
![](https://imgsrc.baidu.com/forum/pic/item/ac4bd11373f08202116bfa060dfbfbedab641b09.png)

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

## 解题领红包之四 {Android 初级题}
**寄语:如果不会解题还想拿分那赶紧来现学现卖吧,只要认真看完并动手练习,肯定能解出来本题,吾爱破解安卓逆向入门教程《安卓逆向这档事》。**
### 游戏
第二个小游戏,居然是抽卡
![](https://imgsrc.baidu.com/forum/pic/item/fcfaaf51f3deb48f6157cbe7b61f3a292df578c9.png)
(吐槽一句:这0.6%概率真有点低啊......)
### BUG玩法
这个程序没有采用数据库方式,**退出重新进入程序即刷新次数为10次**
**于是乎,“只要我不停地抽,0.6%也不算什么”(doge)**
![](https://imgsrc.baidu.com/forum/pic/item/79f0f736afc379315e5aec10adc4b74543a911dc.png)
> **Flag:flag{52pj_HappyNewYear2024}**

### Hacker(破解游戏,调低难度)
**用英文含义命名Activity是个好习惯,至少对于CM而言是这样**
用apktool反编译apk文件,在程序中,我们又发现了作者的信息
![](https://imgsrc.baidu.com/forum/pic/item/203fb80e7bec54e7edf57027ff389b504fc26aea.png)


各位是否发现增加一抽所需要的时间在不断递增?我们简单修改一下增加一抽所需要时间(修改概率、保底同理,只要修改对应的数值就行了)
**将`array_1`中的数据全部改成0x1(即增加一抽仅需要1s),最坏情况下也只需要90s**
![](https://imgsrc.baidu.com/forum/pic/item/f703738da9773912d05bef58be198618367ae28e.png)

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

![](https://imgsrc.baidu.com/forum/pic/item/5ab5c9ea15ce36d361d9e9097cf33a87e950b12e.png)

## 解题领红包之五 {Android 中级题}
**题目简介:我,玄天帝,,解封!!!**
### 游戏
emmm,九宫格图形解锁
![](https://imgsrc.baidu.com/forum/pic/item/8ad4b31c8701a18be9f24776d82f07082838feea.png)

运用强大的搜索引擎,找到了一个类似的开源项目(https://github.com/7heaven/GestureLock)

**小知识,安卓系统的图形是以数字密码形式存放在文件中的,所以推断该图形代表的也是一个数字字符串**
### 静态分析
使用jde反编译得到一堆smali文件,打开以作者名字命名的文件夹
#### GestureUnlock本体
既然是游戏,那么我们就研究一下这款游戏的本体吧,找到以下定义:
```java
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在后续会说到】

![](https://imgsrc.baidu.com/forum/pic/item/8ad4b31c8701a18be25f4e76d82f07082838fe57.png)

后续分析发现输入该密码时会进入函数`isSuccessful`,补充一句该函数是GestureUnlock自带密码正确的回调函数,但此题关键不在此处

```java
public void isSuccessful(String s) {
    Log.e("zj595", s);
}
```
而只有输入"错误"的密码才会进入真正的函数isError

```java
public void isError(String s) {
    Log.e("zj595", s);
    MainActivity.this.checkPassword(s); //checkPassword是个很重要的函数,接下来就会说到
}
```


#### 解密函数
如上部分所述,在MainActivity中提及了一个重要的函数:`checkPassword`,先看它的smali code:
```smali
.method public checkPassword(String)Z
          .registers 11
00000000const/4             v0, 0
:try_2
00000002invoke-virtual      MainActivity->getAssets()AssetManager, p0#读取Assets中的classes.dex
00000008move-result-objectv1
0000000Aconst-string      v2, "classes.dex"
0000000Einvoke-virtual      AssetManager->open(String)InputStream, v1, v2
00000014move-result-objectv1
00000016invoke-virtual      InputStream->available()I, v1
0000001Cmove-result         v2
0000001Enew-array         v2, v2, [B
00000022invoke-virtual      InputStream->read([B)I, v1, v2
00000028new-instance      v3, File
0000002Cconst-string      v4, "data"
00000030invoke-virtual      MainActivity->getDir(String, I)File, p0, v4, v0
00000036move-result-objectv4
00000038const-string      v5, "1.dex"
0000003Cinvoke-direct       File-><init>(File, String)V, v3, v4, v5
00000042new-instance      v4, FileOutputStream
00000046invoke-direct       FileOutputStream-><init>(File)V, v4, v3
0000004Cinvoke-virtual      FileOutputStream->write([B)V, v4, v2#将classes.dex释放至"/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex"
00000052invoke-virtual      FileOutputStream->close()V, v4
00000058invoke-virtual      InputStream->close()V, v1
0000005Econst-string      v1, "dex"
00000062invoke-virtual      MainActivity->getDir(String, I)File, p0, v1, v0
00000068move-result-objectv1
0000006Ainvoke-virtual      Object->getClass()Class, p0
00000070move-result-objectv2
00000072invoke-virtual      Class->getClassLoader()ClassLoader, v2
00000078move-result-objectv2
0000007Anew-instance      v4, DexClassLoader
0000007Einvoke-virtual      File->getAbsolutePath()String, v3
00000084move-result-objectv3
00000086invoke-virtual      File->getAbsolutePath()String, v1
0000008Cmove-result-objectv1
0000008Econst/4             v5, 0
00000090invoke-direct       DexClassLoader-><init>(String, String, String, ClassLoader)V, v4, v3, v1, v5, v2
00000096const-string      v1, "com.zj.wuaipojie2024_2.C"
0000009Ainvoke-virtual      DexClassLoader->loadClass(String)Class, v4, v1# 加载classes.dex里面的C Activity,和上面的C.smali对应
000000A0move-result-objectv1
000000A2const-string      v2, "isValidate"
000000A6const/4             v3, 3
000000A8new-array         v4, v3, [Class
000000ACconst-class         v6, Context
000000B0aput-object         v6, v4, v0
000000B4const-class         v6, String
000000B8const/4             v7, 1
000000BAaput-object         v6, v4, v7
000000BEconst-class         v6, [I
000000C2const/4             v8, 2
000000C4aput-object         v6, v4, v8
000000C8invoke-virtual      Class->getDeclaredMethod(String, [Class)Method, v1, v2, v4
000000CEmove-result-objectv1
000000D0new-array         v2, v3, [Object
000000D4aput-object         p0, v2, v0
000000D8aput-object         p1, v2, v7
000000DCinvoke-virtual      MainActivity->getResources()Resources, p0# actual call site: Landroidx/appcompat/app/AppCompatActivity;->getResources()Landroid/content/res/Resources;
000000E2move-result-objectp1
000000E4sget                v3, R$array->A_offset:I
000000E8invoke-virtual      Resources->getIntArray(I)[I, p1, v3# 传入Gesture构成的数字数组
000000EEmove-result-objectp1
000000F0aput-object         p1, v2, v8
000000F4invoke-virtual      Method->invoke(Object, [Object)Object, v1, v5, v2
000000FAmove-result-objectp1
000000FCcheck-cast          p1, String
00000100if-eqz            p1, :12E            # 如果比较结果等于0,则跳转12E
:104
00000104const-string      v1, "唉!"
00000108invoke-virtual      String->startsWith(String)Z, p1, v1
0000010Emove-result         v1
00000110if-eqz            v1, :12E
:114
00000114iget-object         v1, p0, MainActivity->tvText:TextView
00000118invoke-virtual      TextView->setText(CharSequence)V, v1, p1
0000011Eiget-object         p1, p0, MainActivity->myunlock:GestureUnlock
00000122const/16            v1, 8
00000126invoke-virtual      GestureUnlock->setVisibility(I)V, p1, v1
          .catch Exception {:try_2 .. :tryend_12C} :catch_130
:tryend_12C
0000012Creturn            v7
:12E
0000012Ereturn            v0
:catch_130# used for: Ljava/lang/Exception;
00000130move-exception      p1
00000132invoke-virtual      Exception->printStackTrace()V, p1
00000138return            v0
.end method
```


反编译为java就是:


```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.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方便观看

```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"函数

```java
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, arr_v, arr_v, 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类是这样的**
```java
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"函数是这样的

```java
    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")) = v2;
            byte[] arr_b = D.encodeClassData(hashMap0);
            byteBuffer0.position(v3);
            byteBuffer0.put(arr_b);
            byteBuffer0.position(0x20);
            byte[] arr_b1 = new byte;
            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.read(arr_b);
            ByteBuffer byteBuffer0 = ByteBuffer.wrap(arr_b);
            fileInputStream0.close();
            return byteBuffer0;
      }
      catch(Exception unused_ex) {
            return null;
      }
    }
```
看看chatgpt对以上代码的分析:

这段代码是一个Android应用中的一些Java代码,主要涉及到动态加载Dex文件的操作。下面我来逐个分析这几个函数的作用:

1. `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`:上下文对象,用于获取应用程序的目录等信息。
   - 主要步骤:
   1. 解析Dex文件中的类数据和方法数据。
   2. 根据传入的参数修改方法数据中的某个值。
   3. 将修改后的数据写回ByteBuffer中。
   4. 计算并更新Dex文件的SHA1校验和和校验码。
   5. 将修改后的ByteBuffer写入到应用程序的私有目录中,并返回该文件。

2. `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`:方法参数的类型数组。
   - 主要步骤:
   1. 调用`fix()`函数修复Dex文件,并获取修复后的文件。
   2. 使用修复后的Dex文件创建一个DexClassLoader。
   3. 加载指定类名的类,并获取其中声明的静态方法。
   4. 删除修复后的文件和其在私有目录中的引用。
   5. 返回获取到的静态方法。

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

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

这些函数主要用于动态加载并修复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

![](https://imgsrc.baidu.com/forum/pic/item/0bd162d9f2d3572c6594c4e5cc13632762d0c391.png)

由于dex文件在010Editor中除了头部外并无明显异常情况,因此我们使用(https://github.com/luoyesiqiu/DexRepair)对dex头部进行修复:`java -jar DexRepair.jar /path/to/dex`

修复后头部变为全绿:
![](https://imgsrc.baidu.com/forum/pic/item/30adcbef76094b366d7e40d4e5cc7cd98d109d1f.png)

将修复完的dex命名为`classes.dex`,置入assets文件夹,重新打包、签名
打开jeb调试,发现依然无法正常运行

#### 修复2
单步调试,定位到错误发生点,发现是程序在
```java
try {
    File file0 = new File(context0.getDir("data", 0), "decode.dex");
    if(!file0.exists()) {
      return null;
    }
}
```
处返回了null,即这个file0,也就是`decode.dex`不存在
然后程序跳转到了
```java
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`文件在写入、加载后即会进行自我删除,于是需要在删除代码位置下断点以保存该中间文件进行分析,断点位置如下:

```smali
.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
00000000const/4             v0, 0
:try_2
00000002invoke-static       C->read(Context)ByteBuffer, p0
00000008move-result-objectv1
0000000Aconst/4             v2, 0
0000000Caget                v3, p1, v2
00000010const/4             v4, 1
00000012aget                v4, p1, v4
00000016const/4             v5, 2
00000018aget                p1, p1, v5
0000001Cinvoke-static       C->fix(ByteBuffer, I, I, I, Context)File, v1, v3, v4, p1, p0
00000022move-result-objectp1
00000024invoke-virtual      Object->getClass()Class, p0
0000002Amove-result-objectv1
0000002Cinvoke-virtual      Class->getClassLoader()ClassLoader, v1
00000032move-result-objectv1
00000034const-string      v3, "fixed"
00000038invoke-virtual      Context->getDir(String, I)File, p0, v3, v2
0000003Emove-result-objectp0
00000040new-instance      v2, DexClassLoader
00000044invoke-virtual      File->getAbsolutePath()String, p1
0000004Amove-result-objectv3
0000004Cinvoke-virtual      File->getAbsolutePath()String, p0
00000052move-result-objectv4
00000054invoke-direct       DexClassLoader-><init>(String, String, String, ClassLoader)V, v2, v3, v4, v0, v1
0000005Ainvoke-virtual      DexClassLoader->loadClass(String)Class, v2, p2
00000060move-result-objectp2
00000062invoke-virtual      Class->getDeclaredMethod(String, [Class)Method, p2, p3, p4
00000068move-result-objectp2
0000006Ainvoke-virtual      File->delete()Z, p1#在此处下断点,防止文件删除
00000070new-instance      p3, File
00000074invoke-virtual      File->getName()String, p1
0000007Amove-result-objectp1
0000007Cinvoke-direct       File-><init>(File, String)V, p3, p0, p1
00000082invoke-virtual      File->delete()Z, p3
          .catch Exception {:try_2 .. :tryend_88} :catch_8A
:tryend_88
00000088return-object       p2
:catch_8A# used for: Ljava/lang/Exception;
0000008Amove-exception      p0
0000008Cinvoke-virtual      Exception->printStackTrace()V, p0
00000092return-object       v0
.end method
```

然后在`data/user/0/com.zj.wuaipojie2024_2/app_data`下即可找到修复后的`2.dex`文件

修复后的`2.dex`中的A类如下:

```java
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() < 9 && v < 40; ++v) {
                String s2 = "0485312670fb07047ebd2f19b91e1c5f".substring(v, v + 1);
                if(!stringBuffer0.toString().contains(s2)) {
                  stringBuffer0.append(s2);
                }
            }

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

      return "";
    }
}
```
此处的A.d函数给出了密码`s`的真值以及一个提示

写个小程序获取解锁密码`s`:
```java
import java.io.*;
public class GetPass {
    public static void main(String[] args) {
      StringBuffer stringBuffer0 = new StringBuffer();
      for(int v = 0; stringBuffer0.length() < 9 && v < 40; ++v) {
            String s2 = "0485312670fb07047ebd2f19b91e1c5f".substring(v, v + 1);
            if(!stringBuffer0.toString().contains(s2)) {
                stringBuffer0.append(s2);
            }
      }
      System.out.println(stringBuffer0.toString().toUpperCase());
    }
}
```
输出为:
> 048531267 (九宫格密码的真值,后续会用到)
![](https://imgsrc.baidu.com/forum/pic/item/79f0f736afc37931342bc610adc4b74543a91143.png)

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

找到B类......
(怎么还是讨厌的"?"+s+"?")

```java
public class B {
    public static String d(String s) {
      return "?" + s + "?";
    }
}
```
看来这个B.d还是坏的,还得修复一次

#### 修复3
原先程序里的那个fix函数只能修复A.d部分,接下来修复B.d

回到MainActivity,还是定位到这句
``` java
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这个变量

```java
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
```smali
000000E4sget                v3, R$array->A_offset:I#此处下断点,修改Locals中v3的值为B_offset,即将其改为7F30001h,如图
000000E8invoke-virtual      Resources->getIntArray(I)[I, p1, v3
```
![](https://imgsrc.baidu.com/forum/pic/item/8cb1cb1349540923360d2647d458d109b3de49a7.png)

运行程序,断点至本文"修复2"部分提及的删除函数,得到B.d修复后的`2_new.dex`(此处为了避免与上部分中2.dex混淆,将其命名为2_new.dex,实际由程序生成的文件名仍为2.dex)

打开2_new.dex,反编译得到其中的B类,定位至B.d
```java
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代码反编译得到,无法直接运行):
```java
    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(注册机)
```java
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())) + "}" );
      }
    }
}
```
![](https://imgsrc.baidu.com/forum/pic/item/a9d3fd1f4134970acacc5016d3cad1c8a7865d48.png)

## 总结
正如开头所说,这是我第一次参与红包活动,也是第一次接触安卓类的逆向
因文章篇幅所限,一些细节的内容(比如环境配置、设置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`

得到一文件夹的图片:
![]( https://imgsrc.baidu.com/forum/pic/item/91ef76c6a7efce1b386d5ff3e951f3deb48f6541.png)

接下来就是处理那个切成了四段的二维码,
认真看的小伙伴应该已经发现了,这个二维码的几个帧拼在一起就是一个完整的二维码
使用修图软件框选太麻烦了,有的二维码部分甚至和文字的白色部分连在了一起,
既然我对cv领域比较熟悉,因此我还是用opencv叠加吧


下面是一个用python写的叠加小程序:
```python
import cv2
import os

def add_images_in_folder(folder_path):
    # Get a list of all files in the folder
    image_files =

    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))

    # Loop through the rest of the images and add them to the accumulator
    for image_file in image_files:
      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://imgsrc.baidu.com/forum/pic/item/1c950a7b02087bf431a6c0d0b4d3572c11dfcf45.png)


解码得到网址:https://2024challenge.52pojie.cn/


### Flag1
视频中出现52pojie四个字的时候,后面点阵散开处点阵缺少了一些点,而这些缺少的点就隐藏了flag1,我们用准备部分提到的“叠加小程序”对此部分帧图片进行叠加,得到可见flag

(这个flag也可以直接看,就是有点费眼【doge】)
拼接结果:![](https://imgsrc.baidu.com/forum/pic/item/d6ca7bcb0a46f21fe230c96ab0246b600c33ae87.png)

> **flag1{52pj2024}**

### Flag2
参考去年的[官方题解](https://www.52pojie.cn/thread-1742787-1-1.html)中flag2部分解释:
> 因为页面会自动重定向,我本来想将 X-Dynamic-Flag: flagA{Header X-52PoJie-Uid Not Found} 藏在这个重定向之前的页面的,但是我怕藏得太深了,没这么搞。

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

![](https://imgsrc.baidu.com/forum/pic/item/8cb1cb13495409236527f547d458d109b3de49b9.png)
> **flag2{xHOpRP}**

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

> **flag3{GRsgk2}**

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

![](https://imgsrc.baidu.com/forum/pic/item/7e3e6709c93d70cfede23c34bedcd100baa12beb.png)

下载下来使用图片应用打开即可看到flag4;
![](https://imgsrc.baidu.com/forum/pic/item/b8014a90f603738db4569a1bf51bb051f819ecef.png)

> **flag4{YvJZNS}**

### FlagA
同样是开上述提到的[网址](https://2024challenge.52pojie.cn),F12,查看网络,输入uid登录,会发现有一个叫做`login`的网页写入了cookie信息
而cookie信息中提到了flagA
![](https://imgsrc.baidu.com/forum/pic/item/1c950a7b02087bf43097c1d0b4d3572c11dfcf94.png)

`flagA=guFOgjwXg5haqETMpDMLPyHfY7sP5sf32rW7l3XtVr+9T+LyBKQhmslLNA==; expires=Sat, 17 Feb 2024 06:00:00 GMT; path=/; SameSite=Lax`

看起来flagA被加密了,而且不是base64
但与此同时,uid好像也采用了类似的加密方式,而且网址里好像有个script API用来把cookie转换为uid(嗯?)

```js
      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......
![](https://imgsrc.baidu.com/forum/pic/item/a686c9177f3e67091d90d3c67dc79f3df8dc5573.png)

> **flagA{f96a1e5e}**

**注:flagA每10分钟刷新**

### Flag5
还是那个网页,F12,网页里用注释提到了Flag5
![](https://imgsrc.baidu.com/forum/pic/item/d1a20cf431adcbeffccae83feaaf2edda3cc9f09.png)

我们把style中的属性全部去掉,得到以下一串东西(用图片了,直接发论坛MD渲染会崩):
![](https://imgsrc.baidu.com/forum/pic/item/79f0f736afc379311a8c1010adc4b74543a91126.png)


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

### Flag6
还是那个网页,下方有个flag6的按钮,点击进入flag6
网页很干净,就一个按钮,点了就开始炼丹,电脑风扇呼呼响(doge)
![](https://imgsrc.baidu.com/forum/pic/item/4d086e061d950a7b3aecd8fd4cd162d9f2d3c92a.png)


还是来看看源码吧:

```javascript
document.querySelector("button").addEventListener("click", () => {
const t0 = Date.now();
for (let i = 0; i < 1e8; i++) {
    if ((i & 0x1ffff) === 0x1ffff) {
      const progress = i / 1e8;
      const t = Date.now() - t0;
      console.log(
      `${(progress * 100).toFixed(2)}% ${Math.floor(
          t / 1000
      )}s ETA:${Math.floor(t / progress / 1000)}s`
      );
    }
    if (MD5(String(i)) === "1c450bbafad15ad87c32831fa1a616fc") {
      document.querySelector("#result").textContent = `flag6{${i}}`;
      break;
    }
}
});
```

简而言之就是它跑了一个从0到$10^8 - 1$的数字字符串i,当该字符串的md5为`1c450bbafad15ad87c32831fa1a616fc`时,输出`flag6{${i}}`,否则在console中定期输出计算进度
(好家伙,暴力破解md5,真有你的)


直接md5彩虹表反查,发现是今天的日期,绝了......
![](https://imgsrc.baidu.com/forum/pic/item/7aec54e736d12f2e20cc385909c2d5628535683a.png)

> **flag6{20240217}**

### Flag7
作者在视频里面留下了一个(https://github.com/ganlvtech/52pojie-2024-challenge),打开发现这个:
![](https://imgsrc.baidu.com/forum/pic/item/d4628535e5dde711316bd4cfe1efce1b9d16614a.png)
> "删除不小心提交的flag内容"

提示够明显了,我们直接点击commit寻找历史提交记录,找到了这个
![](https://imgsrc.baidu.com/forum/pic/item/0eb30f2442a7d933c91d734aeb4bd11373f00155.png)

> **flag7{Djl9NQ}**

### Flag8 & FlagB
2048小游戏
![](https://imgsrc.baidu.com/forum/pic/item/dc54564e9258d10908a7388b9758ccbf6c814d47.png)

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

![](https://imgsrc.baidu.com/forum/pic/item/f636afc379310a55a3b04fcdf14543a982261076.png)
> **flag8{OaOjIK}**

接着拿剩下的金币V了作者50(doge)
> 竟然真的有人v我50,真的太感动了。作为奖励呢,我就提示你一下吧,关键词是“溢出”。

首先想到的肯定是多买点,然后让它溢出,可惜有可能弄得太猛了,导致溢出后金币数量增加了,作者也想到了这个,购买请求直接被拦截了
![](https://imgsrc.baidu.com/forum/pic/item/4afbfbedab64034f7341533fe9c379310a551d7e.png)

猜想是做了检验,即购买后金币数量不能高于现有数量。
手算了几个临界值,罢了,完全没用,
因为题目没有写明白它后端到底用的啥数据类型,因此放弃思考,直接用request组件爆破
```python
# 导入requests模块
import requests
for i in :
    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*

![](https://imgsrc.baidu.com/forum/pic/item/43a7d933c895d143522d771a35f082025aaf07b4.png)

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

### Flag9
之前说到的那一串符号,调节窗口大小,即可看到立体的Flag9
![](https://imgsrc.baidu.com/forum/pic/item/a8014c086e061d953eb7a4013df40ad162d9ca12.png)

> **flag9{KHTALK}**

### Flag10
和Flag4是一张图片,图片misc类嘛,首先用(https://github.com/ReFirmLabs/binwalk)看看是不是有什么不对劲的地方。
好像还挺正常的,没有隐藏压缩包也没有藏图
![](https://imgsrc.baidu.com/forum/pic/item/d52a2834349b033b387865e353ce36d3d539bd13.png)

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

![](https://imgsrc.baidu.com/forum/pic/item/83025aafa40f4bfbfb22076d454f78f0f736181c.png)

**所以,做这题时 千 万 不 要 用edge下载图片!!**
![两个文件的对比图](https://imgsrc.baidu.com/forum/pic/item/faf2b2119313b07ebe50c9314ad7912397dd8c1e.png)

发现这个问题以后,直接用curl命令下载图片`curl -l https://2024challenge.52pojie.cn/flag4_flag10.png -o ./flag4_flag10.png`

接下来就简单了,用(https://github.com/Giotino/stegsolve/releases)中的Analyse-Stereogram Solver(立体视图)工具,设置偏移量为1或2即可看到隐藏的flag10

上图是curl下载图片(含隐写信息);下图是edge下载图片(无隐写信息)
!(https://imgsrc.baidu.com/forum/pic/item/bd3eb13533fa828b18cf04dabb1f4134970a5a1f.png)
!(https://imgsrc.baidu.com/forum/pic/item/0dd7912397dda144d7e7f58bf4b7d0a20cf48619.png)

> **flag10{6BxMkW}**

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

> **flag11{HPQfVF}**

### Flag12
WebAssembly(Wasm)技术,直接看关键部分
```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。
具体而言,代码执行以下步骤:
1. i32.const 1213159497:将值1213159497(0x483CEEE9)压入堆栈。
2. i32.const 0:将值0压入堆栈。
3. local.get \\$var0:将函数参数\$var0的值压入堆栈。
4. i32.const 1103515245:将值1103515245(0x41C64E6D)压入堆栈。
5. i32.mul:将栈顶两个值相乘。
6. i32.const 1:将值1压入堆栈。
7. i32.eq:比较栈顶两个值是否相等,如果相等则将1压入堆栈,否则将0压入堆栈。
8. select:根据栈顶的布尔值选择两个值中的一个放回栈顶。

因此,这段代码的主要目的是将参数\$var0与特定的数值进行处理,并根据结果返回1或0。

又是溢出问题,即1103515245*`输入值` = 1,那么这个输入值一定很大(超出上限),
这边提到了i32,即32位整型数据,最大值为2^31-1 = 2147483647
写个C语言程序跑一下
```cpp
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
int main()
{
    for(long j = 0; j<= 4294967295; j++){
      int i = 1103515245;
      if(i*j == 1) {
            cout<<j<<endl;
            break;
      }
    }
    return 0;
}
```
嗯?结果跑出来个负数?不管了,填进去。
> -289805467

这也能出答案是我没想到的......看来是满足条件就能出......
![](https://imgsrc.baidu.com/forum/pic/item/10dfa9ec8a1363275c94500ad78fa0ec08fac78a.png)

> **flag12{HOXI}**

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

我搭出的阵:
![](https://imgsrc.baidu.com/forum/pic/item/72f082025aafa40fb43357e4ed64034f78f01916.png)

下面来简单看一下这个网页的逻辑:
```javascript
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**


Hmily 发表于 2024-2-26 11:38

cattie牛批!

正己 发表于 2024-2-25 22:40

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

看我课程里的

PastYiHJ 发表于 2024-2-25 08:10

多谢大佬的分析教程

debug_cat 发表于 2024-2-25 09:17

大佬带带安卓高级的{:1_918:}

cattie 发表于 2024-2-25 09:27

本帖最后由 cattie 于 2024-2-25 16:53 编辑

debug_cat 发表于 2024-2-25 09:17
大佬带带安卓高级的
后面的高级题太菜了不会做
最主要后来没时间分析了{:301_1004:}有空一定玩玩
@正己

debug_cat 发表于 2024-2-25 09:39

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

高级题没头绪,头大:'(weeqw

长得帅活得久 发表于 2024-2-25 12:32

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

cattie 发表于 2024-2-25 12:38

本帖最后由 cattie 于 2024-2-25 12:41 编辑

长得帅活得久 发表于 2024-2-25 12:32
大佬,直接核心破解安装是咋弄的
lucky patcher里面去除签名校验,
然后系统安装相应的核心破解模块(lsp,禁用安卓的签名检验)
参考这个:https://zhuanlan.zhihu.com/p/654235511

zouzhiqiang 发表于 2024-2-25 13:03

两个Android初级题,简单看了一下代码,发现一个更bug的
初级题一:直接解压apk,找到ys.mp4文件,放入Winhex中查看,直接滑到最下面,就是flag
初级题二:用objection ,查看当前Activity列表,发现有个FlagActivity,直接启动flagActivity就行了

正己 发表于 2024-2-25 17:04

大佬可以看看新工具,Lucky Patcher应该没有那么好用

cattie 发表于 2024-2-25 17:10

正己 发表于 2024-2-25 17:04
大佬可以看看新工具,Lucky Patcher应该没有那么好用

嗯,因为模拟器里面有这个所以直接用了,后来我就用别的打测试签名了。
页: [1] 2 3
查看完整版本: 吾爱破解2024红包题部分WriteUP