K哥爬虫 发表于 2024-2-27 17:41

吾爱破解2024春节解题领红包活动,喜迎新春~

!(https://v1.ax1x.com/2024/02/26/7w5GGa.jpg)

(图作者 | 吾爱破解@Aoemax)

## 前言

K哥在这里,先祝各位小伙伴们新春快乐,财源广进,阖家幸福!

吾爱破解每年都有个解题领红包活动,今年也不例外,需要我们使出看家逆向本领来分析内容获得口令红包,根据难度等级不同会获得不同数量的吾爱币,活动持续到元宵节结束。活动一共有十个题,本文分享过年期间抽空做的几个题的相关思路。文章很早就写好了,不过遵循论坛的规则,延迟至元宵节之后发布。

活动地址:https://www.52pojie.cn/thread-1889163-1-1.html

## Windows 初级

直接使用 IDA 打开,先运行一次,随便输入:

```
Please input password:
aaa
Error, please try again
```

搜索对应字符串:

!(https://v1.ax1x.com/2024/02/26/7w5xz7.gif)

主要逻辑就是下面部分,先判断长度是否等于 36,再逐字节判断 *v10 != *v9,那么直接动态调试:

```java
if ( v36 == 36 )
{
    sub_5B2490(&v27, Src);
    sub_5B1FE0(Block, -3, (char *)v27, v28);
    LOBYTE(v38) = 2;
    v9 = Block;
    v10 = v35;
    if ( v34 >= 0x10 )
      v9 = (char **)Block;
    if ( v6 >= 0x10 )
      v10 = v7;
    if ( Block != (char *)36 )
      goto LABEL_19;
    v11 = 32;
    while ( 1 )
    {
      v12 = *v10;
      if ( *v10 != *v9 )
      break;
      ++v10;
      ++v9;
      v14 = v11 < 4;
      v11 -= 4;
      if ( v14 )
      {
      v13 = 0;
      goto LABEL_18;
      }
    }
    。。。。。。。。。。。。。。。。。。。。
    LABEL_18:
    v18 = "Success";
    if ( v13 )
    LABEL_19:
    v18 = "Wrong,please try again.";
    。。。。。。。。。。。。。。。。。。。。
    sub_5BA6EE("Pause");
}
else
{
    v8 = sub_5B27D0(v5, "Error, please try again");
    sub_5B2A80((int)v8);
    sub_5BA6EE("Pause");
}
```

if ( *v10 != *v9 ) 这里下个断点,输入 "a"×36,很明显是明文对比了:

!(https://v1.ax1x.com/2024/02/26/7w5VHV.png)

然后修改下数据类型,查看对应值,很明显 flag 就是 fl@g{H@ppy_N3w_e@r!2o24!Fighting!!!}:

!(https://v1.ax1x.com/2024/02/26/7w55oI.gif)

还原也很简单就不详细分析了,唯一需要注意 IDA 识别 \x80 成了字符 ?,不要直接去复制了:

```python
bytes().decode("utf-8")
```

## 安卓初级1

### 抓猫能手

小小猫咪竟敢班门弄斧,偷走我的 flag,好歹我也是猫咪猎手:

!(https://v1.ax1x.com/2024/02/26/7w5W1L.png)

抓到猫咪过后会播放“原神启动”,视频最后会出现 flag,千万不要提前关闭:

!(https://v1.ax1x.com/2024/02/26/7w5c2J.jpg)

### JS 调试

抓猫部分是 html,使用的 webview,参考这篇文章:

```
https://www.52pojie.cn/thread-967665-1-1.html
```

开启调试,建议使用 Edge。

直接搜索失败的提示"汗流浃背了吧,老弟!",可以看到失败和成功调用函数,在失败处下个断点,随便点击:

!(https://v1.ax1x.com/2024/02/26/7w5aQG.gif)

### JAVA 分析

从上面内容当抓到小猫过后,回调了 onSolverReturnValue:

```java
public void onSolverReturnValue(int i) {
    if (i == -1) {
      this.mContext.startActivity(new Intent(this.mContext, YSQDActivity.class));
    }
}
```

onSolverReturnValue 又加载 YSQDActivity:

```java
String filePath = "/data/user/0/com.zj.wuaipojie2024_1/files/ys.mp4";
   
public void onCreate(Bundle bundle) {
    。。。。。。。。。。
    playVideo(this.filePath);
}

public void playVideo(String str) {
    .............................
    videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
      @Override
      public void onPrepared(MediaPlayer mediaPlayer) {
            mediaPlayer.setVideoScalingMode(1);//播放视频
      }
    });
    videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
      @Override
      public void onCompletion(MediaPlayer mediaPlayer) {//播放结束设置flag
            YSQDActivity.this.tv.setText(YSQDActivity.extractDataFromFile(YSQDActivity.this.filePath));
      }
    });
    .............
}
```

播放结束时调用了 extractDataFromFile 获取 flag,就是明文藏在视频中:

```java
public static String extractDataFromFile(String str) {
    RandomAccessFile randomAccessFile = new RandomAccessFile(str, "r");
    long length = randomAccessFile.length();
    for (long max = Math.max(length - 30, 0L); max < length; max++) {
      if (new String(bArr, StandardCharsets.UTF_8).indexOf("flag{") != -1) {
            String str3 = str2.substring(indexOf).split("\\}") + "}";
            randomAccessFile.close();
            return str3;
      }
    }
    randomAccessFile.close();
    return null;
}
```

!(https://v1.ax1x.com/2024/02/26/7w5DvB.png)

## 安卓初级2

众所周知原神是一个抽卡游戏,原神启动过后先来一发,抽中过后就会出现 flag:

!(https://v1.ax1x.com/2024/02/26/7w5Ont.png)

充钱是不可能充钱的,这辈子都不可能的:

!(https://v1.ax1x.com/2024/02/26/7w5ULb.gif)

要充钱首先就要找到充钱入口,简单搜索一下:

```java
public class WishActivity extends h {
    public int[] o = {10, 0, 0};
    public int[] p = {1, 2, 4, 8, 16, 32, 64, 128};
    public final void run() {
      textView.setText(iArr2 < 10 ? String.format(Locale.SIMPLIFIED_CHINESE, "当前已完成%d次祈愿,拥有%d个纠缠之缘\n%d秒后将为你补充一个", Integer.valueOf(iArr2), Integer.valueOf(wishActivity.o), Integer.valueOf(wishActivity.o)) : String.format(Locale.SIMPLIFIED_CHINESE, "当前已完成%d次祈愿,当前拥有%d个纠缠之缘\n纠缠之缘已满,%d秒后将溢出一个,请尽快使用!", Integer.valueOf(iArr2), Integer.valueOf(wishActivity.o), Integer.valueOf(wishActivity.o)));
    }
};
```

可以看到充钱入口就在 wishActivity.o,直接用 Frida 充:

```java
Java.perform(function x() {
    let WishActivity = Java.choose("com.kbtx.redpack_simple.WishActivity", {
      onMatch: function (instance) {
            console.log(`WishActivity instance found: ${instance}`);
            console.log(`WishActivity instance found: ${instance.o.value}`);
            instance.o.value = 648*10;

      },
      onComplete: function () {
      }
    });
});
```

只能充一点点不能充多了,冲多了会封号:

!(https://v1.ax1x.com/2024/02/26/7w5Y9w.png)

充完钱就不用我教了吧。

下面是判断是否抽中,抽中过后,又调用了 FlagActivity:

```java
if (random < (iArr2 <= 80 ? 0.006d : (iArr2 - 80) * 0.1d)) {
    Toast.makeText(wishActivity, "恭喜你十连出金了,奖品为 flag 提示!", 1).show();
    wishActivity.startActivity(new Intent(wishActivity, FlagActivity.class));
    return;
}
```

在 FlagActivity 中,先获取了签名,再和 o 异或,就得到了 flag:

```java
public static byte[] o = {86, -18, 98, 103, 75, -73, 51, -104, 104, 94, 73, 81, 125, 118, 112, 100, -29, 63, -33, -110, 108, 115, 51, 59, 55, 52, 77};

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
try {
    signatureArr = getPackageManager().getPackageInfo(getPackageName(), 64).signatures;
} catch (PackageManager.NameNotFoundException unused) {
    bArr = new byte;
}
if (signatureArr != null && signatureArr.length >= 1) {
    byte[] byteArray = signatureArr.toByteArray();
    ByteBuffer allocate = ByteBuffer.allocate(bArr2.length);
    for (int i = 0; i < bArr2.length; i++) {
      allocate.put((byte) (bArr2 ^ byteArray));
    }
    bArr = allocate.array();
    StringBuilder d = a.d("for honest players only: \n");
    d.append(new String(bArr));
    ((TextView) findViewById(R.id.tvFlagHint)).setText(d.toString());
}
```

直接利用 (http://keystore-explorer.org/),从 /META-INF/CERT.RSA 导出秘钥就行:

!(https://v1.ax1x.com/2024/02/26/7w5rH6.gif)



```python
import base64
key = '''MIIDADCCAegCAQEwDQYJKoZIhvcNAQELBQAwRjEQMA4GA1UEAwwHa2J0eHdlcjEQ
MA4GA1UECwwHNTJwb2ppZTEQMA4GA1UECgwHNTJwb2ppZTEOMAwGA1UEBwwFQ2hp
bmEwHhcNMjQwMTE2MDYzMzIzWhcNNDkwMTA5MDYzMzIzWjBGMRAwDgYDVQQDDAdr
YnR4d2VyMRAwDgYDVQQLDAc1MnBvamllMRAwDgYDVQQKDAc1MnBvamllMQ4wDAYD
VQQHDAVDaGluYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIBIBBNf
V8FTmAmp9ikd0NqDxfn8V8rxmaSM/je5oMxGoQUhMqY0TjCaMbgO5xXf/L0gf4Sw
fmIMi8MjKwkwUEc/gp7LdKVF7o/UKf6uhIDkKEw1vGncQ9PBMOv3sKFsbRCFdhPC
JCAq53Em/P3JZCFEFYKH/noZaWO8UqR7uULw916wWSNr+mTFJxjHNUekw2LxF07G
QrmKMaTXy+jpkd+ifbcANdRRyHm13vEtu32xn9WrIREQJWxBVs0L5z0i0sBgMUTe
oY5lehLAwBRrpcXrprlzoie4FfyO/tTEonVHcYVL08BEaG7L5lBaVA56+qCZkzlB
C1qf64JkB0UsKIsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXhAk7ZWZLNjYgzTq
82D9VpntfSMzY03e1l6c2mIiu1rmgYnbavtYmMqfNDeVnbLlDObRn8O5gu3n6e1d
2SSI3tZpKK1ZOf3zGLF7SpXwIFu22iej3k97aXANlKJegHZ9JWtjABTiVGSLKjfW
iZWe9HKTp3LBUJ2zGw3e03eWT+kzZtjvgI4gfRsji7vVG2odODMODCm+4a/dBnTl
ADtM0lVdJaDPUj8ReR0ql/99EyNUMv7wtE+3o0xpCrUd5NVLp4doEusfaRnSvS35
fDfp6SfODQ9BqE9TPgEPyOGn+iA8HHw+XQhzsrn8bNdNnlOBMsbXJcMFvF92Cw+4
cQGoog=='''
sign = base64.b64decode(key.replace('\n', ''))
bArr2 = [86, -18, 98, 103, 75, -73, 51, -104, 104, 94, 73, 81, 125, 118, 112, 100, -29, 63, -33, -110, 108, 115, 51, 59,
         55, 52, 77];
bArr2 = [(b & 0xFF ^ sign) for i, b in enumerate(bArr2)]
print(bytes(bArr2).decode('utf-8'))
```

## 安卓中级

### 玄天帝重生

2006 年正月初五 2/2 10:00 玄天帝重生,18 年后,偶遇前世一个宝藏:

!(https://v1.ax1x.com/2024/02/26/7w5t1O.png)

!(https://v1.ax1x.com/2024/02/26/7w5z6Q.png)

一代玄天帝还没有发育起来就被消灭了,预知后事如何,欢迎吃席。

### 一线生机

在刺客联盟追捕下,少年玄天帝不幸坠落悬崖,按照剧情发展应该又有奇遇了。在坠落悬崖时不幸碰到了头,他突然想起 Android Studio 可以查看部分日志:

!(https://v1.ax1x.com/2024/02/26/7w5CQf.png)

发现 checksum 不匹配,查看对应代码,dex 位于 /assets/classes.dex 然后释放到根目录改名为 1.dex 了,那么直接用 np 修复文件头,在替换回去:

```java
public boolean checkPassword(String str) {
    try {
      InputStream open = getAssets().open("classes.dex");
      byte[] bArr = new byte;
      open.read(bArr);
      File file = new File(getDir("data", 0), "1.dex");
      FileOutputStream fileOutputStream = new FileOutputStream(file);
      fileOutputStream.write(bArr);
      fileOutputStream.close();
      open.close();
      String str2 = (String) new DexClassLoader(file.getAbsolutePath(), getDir("dex", 0).getAbsolutePath(), null, getClass().getClassLoader()).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[].class).invoke(null, this, str, getResources().getIntArray(R.array.A_offset));
      if (str2 == null || !str2.startsWith("唉!")) {
            return false;
      }
      this.tvText.setText(str2);
      this.myunlock.setVisibility(8);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
}
```

```js
// frida

var fileOutputStream = Java.use("java.io.FileOutputStream");
var a = 1;
fileOutputStream.write.overload('[B').implementation = function (bArr) {
    console.log("write: ");
    if (a % 2) {
      bArr = // 修复后的字节,也可也只替换头
    }
    a++;
    var ret = fileOutputStream.write.overload('[B').call(this, bArr);
    return ret;
};
```

然后运行,又报错了,查看对应代码:

!(https://v1.ax1x.com/2024/02/26/7w5Jvc.png)

```java
public static HashMap<String, Integer> getClassDefData(ByteBuffer byteBuffer, int i) {
    if (byteBuffer == null) {
      throw new IllegalArgumentException("Buffer cannot be null");
    }
}

```

byteBuffer 读取是在 fix(read(context), iArr, iArr, iArr, context),按提示在 fix dex 而这里缺读取的 decode.dex,前面保存的又是 1.dex,那么直接把 1.dex 改个名字/data/user/0/com.zj.wuaipojie2024_2/app_data/decode.dex:

```java
private static ByteBuffer read(Context context) {
    File file = new File(context.getDir("data", 0), "decode.dex");
    if (file.exists()) {
      FileInputStream fileInputStream = new FileInputStream(file);
      byte[] bArr = new byte;
      fileInputStream.read(bArr);
      ByteBuffer wrap = ByteBuffer.wrap(bArr);
      fileInputStream.close();
      return wrap;
    }
    return null;
}
```

还注意到在获取了对应方法后,删掉了修复后的 dex,那么直接 hook 删除函数,或者 hook 写入函数传到电脑里:

!(https://v1.ax1x.com/2024/02/26/7w5XI3.png)

```JavaScript
var deleteFile = Java.use("java.io.File").delete;
deleteFile.implementation = function () {
    console.log("delete file: " + this);
};
```

查看修复成功后的函数,很容易得到密码 048531267,也没啥用,还有个提示:

!(https://v1.ax1x.com/2024/02/26/7w5nLj.png)

结果发现没有任何有用信息,参考之前 A.d 的修复,再加上之前的传入了一个参数 A_offset,附近还有个 B_offset:

!(https://v1.ax1x.com/2024/02/26/7w5Ix5.png)

那么 hook 一下,修改为 B 的偏移,先把之前修复好了 2.dex 的改为 decode.dex,这样 A 就是修复好了的:

```JavaScript
let MainActivity = Java.use("com.zj.wuaipojie2024_2.MainActivity");
MainActivity["checkPassword"].implementation = function (str) {
    str = "048531267"
    console.log(`MainActivity.checkPassword is called: str=${str}`);
    let result = this["checkPassword"](str);
    return result;
};
let AppCompatActivity = Java.use("androidx.appcompat.app.AppCompatActivity");
AppCompatActivity["getResources"].implementation = function () {
    let result = this["getResources"]();
    console.log(result.getIntArray(0x7f030001));
    console.log(result);
    return result;
};
```

然后运行,首先密码正确:

!(https://v1.ax1x.com/2024/02/26/7w5M4m.png)

然后函数也修复出来了:

!(https://v1.ax1x.com/2024/02/26/7w5i94.png)

防止魔改主动调用一下:

```JavaScript
Java.enumerateClassLoaders({
    "onMatch": function (loader) {
      if (loader.toString().indexOf("dalvik.system.DexClassLoader") !== -1) {
            Java.classFactory.loader = loader;
            console.log(loader);
      }
    },
    "onComplete": function () {
      console.log("success");
    }
});
let Utils = Java.use("com.zj.wuaipojie2024_2.Utils");
let password_uid="048531267"
let str = Java.use("java.lang.String").$new(password_uid);
let bArr = str.getBytes();
let sha1 = Utils.getSha1(bArr);
let md5 = Utils.md5(sha1);
console.log(`机缘是${md5}`);
```

## Web

flag3{GRsgk2} 视频开头变化的。

flag1{52pj2024} 2-3s 左右变化的。

flag2{xHOpRP} 扫描二维码 直接访问 https://2024challenge.52pojie.cn/会重定向,在响应头里面 X-Flag2: flag2{xHOpRP}:

!(https://v1.ax1x.com/2024/02/26/7w5sKh.png)

flag4{YvJZNS} 网站会加载一张图片 flag4_flag10.png 里面直接显示 4。

flagA ,登陆时后台返回了加密 flagA 以及 UID,参考每次会请求 https://2024challenge.52pojie.cn/auth/uid 这个地址去解密 uid,直接修改 ck:

```python
cookies = {
    "uid": "Uu6S/LKGcHP....ahI9KitSRsMFLDNu7ecW2TqkIcWBA==",//修改为flagA=****里面的值
}
url = "https://2024challenge.52pojie.cn/auth/uid"
response = requests.get(url, cookies=cookies)
flagA{ea239d69}
```

flag5{P3prqF}网页中有提示 `<!-- flag5 flag9 -->` 以-.. 换行,可以看出另一个 flag,微调一下,再缩放:

!(https://v1.ax1x.com/2024/02/26/7w52P9.png)

flag9{KHTALK}:

!(https://v1.ax1x.com/2024/02/26/7w566Y.png)

flag6{20240217}计算 md5(*)==1c450bbafad15ad87c32831fa1a616fc,直接让网页跑一会就行,或者在 https://www.cmd5.com/ 这里查询。

flag7{Djl9NQ}   视频中的 Git 地址里面,利用历史记录查看 https://github.com/ganlvtech/52pojie-2024-challenge/commit/6bbac038c4813fbc5d129a8d605471ea2e374786。

flag8{OaOjIK}   flagB 玩游戏就行,如果你买了v50,会给你提示溢出,直接买 2**62 =4611686018427387904 个,不出意外的话可以购买成功。

flag10{6BxMkW}flag4_flag10.png 里面,没看懂随便改了一个二进制就有了:

!(https://v1.ax1x.com/2024/02/26/7w5ZNH.png)

实际上应该是两个图层,默认是黑色的,flag4 是白色可以直接看,flag10 和背景一样导致显示不出来

直接在这个网站 https://www.georgeom.net/StegOnline/image,选择 lnverse (RGBA)   甚至你还可以直接改后缀为 mp4:

!(https://v1.ax1x.com/2024/02/26/7w5dvZ.png)

!(https://v1.ax1x.com/2024/02/26/7wA90q.png)

flag11{HPQfVF} 拼图,网站给了提示:

```
:root {
    --var1: 0; /* 在 0 ~ 100 范围内找到一个合适的值 */
    --var2: 0; /* 在 0 ~ 100 范围内找到一个合适的值 */
}

#a000 {
    position: absolute;
    left: 0;
    top: 0;
    width: 30px;
    height: 30px;
    background: url(flag11.png) 0px 0px;
    transform: translate(calc(942.5135817416999px + 1.0215884355337748px * var(--var1) + 0.24768196677010001px * var(--var2)), calc(224.16483995058888px + 2.9293942195858147px * var(--var1) + 0.8924085229409133px * var(--var2)));
}
```

transform 里面对应的应该是整数甚至可以说是 30 整数倍数才行,不然几乎不可能还原图片:

```python
a = 942.5135817416999
b = 1.0215884355337748
c = 0.24768196677010001
d = 224.16483995058888
e = 2.9293942195858147
f = 0.8924085229409133
for i in range(0, 100):
    for j in range(0, 100):
      dd= a+b*i+c*j
      ww= d+e*i+f*j
      print(dd, ww)
      print(i, j)
//1020.0 450.00000000000006
71 20
```

!(https://v1.ax1x.com/2024/02/26/7w5eIU.png)

flag12{HOXI} 很简单 wasm,修改一下 num 就行:

```javascript
let num = instance.exports.get_flag12(secret);//1213159497   (int32)(secret* 1103515245)!= 1 ? 0: 1213159497
let str = '';
while (num > 0) {
    str = String.fromCodePoint(num & 0xff) + str;
    num >>= 8;
}
return `flag12{${str}}`;
```

flagC 没看懂考的什么直接用他给案例图片,根据返回提示修改,一开始提示物体太多,删减 classes 到 4 个后就行了:

```json
{"hint":"物体太多了","labels":["car 种类错误","bus 种类错误","truck 种类错误","train 种类错误","fire hydrant 种类错误","motorcycle 种类正确 位置错误","traffic light 种类正确 位置错误","traffic light 种类正确 位置错误","cat 种类错误","bicycle 种类错误","person 种类正确 位置正确","boat 种类错误","traffic light 种类正确 位置错误","airplane 种类正确 位置正确"],"colors":["ff9999","ff9999","ff9999","ff9999","ff9999","ffff99","ffff99","ffff99","ff9999","ff9999","99ff99","ff9999","ffff99","99ff99"]}
```

我这运气好,刚好有四种 traffic lightperson airplane motorcycle 随便填按提示修改就行,最后提交的内容:

```python
data = {
    "boxes": [
      0.0071830302476882935,
      0.5186262726783752,
      0.4009798765182495,
      0.6479262709617615,
      0.4077116847038269,
      0.5121312141418457,
      0.7820706963539124,
      0.776945948600769,
      0.31250375509262085,
      0.2294374704360962,
      0.7281658053398132,
      0.4627001881599426,
      0.002122640609741211,
      0.8341933488845825,
      0.3802390992641449,
      0.9994925260543823
    ],
    "scores": [
      0.8933814167976379,
      0.8905049562454224,
      0.884631872177124,
      0.8726911544799805
    ],
    "classes": [
      0,
      9,
      3,
      4
    ]
}

{"hint":"flagC{b8ff9fbc} 过期时间: 2024-02-17 22:50:00","labels":["person 种类正确 位置正确","traffic light 种类正确 位置正确","motorcycle 种类正确 位置正确","airplane 种类正确 位置正确"],"colors":["99ff99","99ff99","99ff99","99ff99"]}
```

Ishisashi 发表于 2024-2-28 13:00

然后运行,首先密码正确:
我解这题的时候把 A_offset 改成 B_offset,decode.dex 塞入修复过 A 后的 dex,输入手势密码还是没能触发更改文本的效果。

正己 发表于 2024-2-27 17:55

我,玄天帝,打钱!
"然后运行,首先密码正确:"这段文字后面的图配错啦,师傅

雨落惊鸿, 发表于 2024-2-27 22:49

感谢大佬分享

zhongxiaoqian 发表于 2024-2-28 08:36

不错呀!!谢谢

新提醒 发表于 2024-2-28 08:46

假期玩的太嗨,没时间参与

Minas 发表于 2024-2-28 13:22

很不错,谢谢分享!

debug_cat 发表于 2024-2-28 16:04

Ishisashi 发表于 2024-2-28 13:00
我解这题的时候把 A_offset 改成 B_offset,decode.dex 塞入修复过 A 后的 dex,输入手势密码还是没能触 ...

看看log有没有什么错误信息,dex如果加载失败,是不会触发逻辑的。

辞年小妖 发表于 2024-2-28 17:51



感谢大佬分享

YZJCX330 发表于 2024-2-28 19:15

特别感谢,十分感谢&#129392;
页: [1] 2 3 4 5 6
查看完整版本: 吾爱破解2024春节解题领红包活动,喜迎新春~