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

前言

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

吾爱破解每年都有个解题领红包流动,往年也不例外,须要咱们使出看家逆向本事来剖析内容取得口令红包,依据难度等级不同会取得不同数量的吾爱币,流动继续到元宵节完结。流动一共有十个题,本文分享过年期间抽空做的几个题的相干思路。文章很早就写好了,不过遵循论坛的规定,提早至元宵节之后公布。

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

Windows 高级

间接应用 IDA 关上,先运行一次,轻易输出:

Please input password:aaaError, please try again

搜寻对应字符串:

次要逻辑就是上面局部,先判断长度是否等于 36,再逐字节判断 v10 != v9,那么间接动静调试:

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[0];    if ( v6 >= 0x10 )      v10 = v7;    if ( Block[4] != (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,很显著是明文比照了:

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

还原也很简略就不详细分析了,惟一须要留神 IDA 辨认 \x80 成了字符 ,不要间接去复制了:

bytes([a - 3 for a in b"ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$\x80"]).decode("utf-8")

安卓高级1

抓猫能手

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

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

JS 调试

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

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

开启调试,倡议应用 Edge。

间接搜寻失败的提醒 "挥汗如雨了吧,老弟!",能够看到失败和胜利调用函数,在失败处下个断点,轻易点击:

JAVA 剖析

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

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

onSolverReturnValue 又加载 YSQDActivity:

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,就是明文藏在视频中:

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("\\}")[0] + "}";            randomAccessFile.close();            return str3;        }    }    randomAccessFile.close();    return null;}

安卓高级2

家喻户晓原神是一个抽卡游戏,原神启动过后先来一发,抽中过后就会呈现 flag:

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

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

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[0] < 10 ? String.format(Locale.SIMPLIFIED_CHINESE, "以后已实现%d次祈愿,领有%d个纠缠之缘\n%d秒后将为你补充一个", Integer.valueOf(iArr2[1]), Integer.valueOf(wishActivity.o[0]), Integer.valueOf(wishActivity.o[2])) : String.format(Locale.SIMPLIFIED_CHINESE, "以后已实现%d次祈愿,以后领有%d个纠缠之缘\n纠缠之缘已满,%d秒后将溢出一个,请尽快应用!", Integer.valueOf(iArr2[1]), Integer.valueOf(wishActivity.o[0]), Integer.valueOf(wishActivity.o[2])));    }};

能够看到充钱入口就在 wishActivity.o[0],间接用 Frida 充:

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[0] = 648*10;        },        onComplete: function () {        }    });});

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

充完钱就不必我教了吧。

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

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

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

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[0];}if (signatureArr != null && signatureArr.length >= 1) {    byte[] byteArray = signatureArr[0].toByteArray();    ByteBuffer allocate = ByteBuffer.allocate(bArr2.length);    for (int i = 0; i < bArr2.length; i++) {        allocate.put((byte) (bArr2[i] ^ byteArray[i % byteArray.length]));    }    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());}

间接利用 KeyStore Explorer,从 /META-INF/CERT.RSA 导出秘钥就行:

import base64key = '''MIIDADCCAegCAQEwDQYJKoZIhvcNAQELBQAwRjEQMA4GA1UEAwwHa2J0eHdlcjEQMA4GA1UECwwHNTJwb2ppZTEQMA4GA1UECgwHNTJwb2ppZTEOMAwGA1UEBwwFQ2hpbmEwHhcNMjQwMTE2MDYzMzIzWhcNNDkwMTA5MDYzMzIzWjBGMRAwDgYDVQQDDAdrYnR4d2VyMRAwDgYDVQQLDAc1MnBvamllMRAwDgYDVQQKDAc1MnBvamllMQ4wDAYDVQQHDAVDaGluYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIBIBBNfV8FTmAmp9ikd0NqDxfn8V8rxmaSM/je5oMxGoQUhMqY0TjCaMbgO5xXf/L0gf4SwfmIMi8MjKwkwUEc/gp7LdKVF7o/UKf6uhIDkKEw1vGncQ9PBMOv3sKFsbRCFdhPCJCAq53Em/P3JZCFEFYKH/noZaWO8UqR7uULw916wWSNr+mTFJxjHNUekw2LxF07GQrmKMaTXy+jpkd+ifbcANdRRyHm13vEtu32xn9WrIREQJWxBVs0L5z0i0sBgMUTeoY5lehLAwBRrpcXrprlzoie4FfyO/tTEonVHcYVL08BEaG7L5lBaVA56+qCZkzlBC1qf64JkB0UsKIsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXhAk7ZWZLNjYgzTq82D9VpntfSMzY03e1l6c2mIiu1rmgYnbavtYmMqfNDeVnbLlDObRn8O5gu3n6e1d2SSI3tZpKK1ZOf3zGLF7SpXwIFu22iej3k97aXANlKJegHZ9JWtjABTiVGSLKjfWiZWe9HKTp3LBUJ2zGw3e03eWT+kzZtjvgI4gfRsji7vVG2odODMODCm+4a/dBnTlADtM0lVdJaDPUj8ReR0ql/99EyNUMv7wtE+3o0xpCrUd5NVLp4doEusfaRnSvS35fDfp6SfODQ9BqE9TPgEPyOGn+iA8HHw+XQhzsrn8bNdNnlOBMsbXJcMFvF92Cw+4cQGoog=='''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[i % len(sign)]) for i, b in enumerate(bArr2)]print(bytes(bArr2).decode('utf-8'))

安卓中级

玄天帝新生

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

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

一线生机

在刺客联盟追捕下,少年玄天帝可怜坠落悬崖,依照剧情倒退应该又有奇遇了。在坠落悬崖时可怜碰到了头,他忽然想起 Android Studio 能够查看局部日志:

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

public boolean checkPassword(String str) {    try {        InputStream open = getAssets().open("classes.dex");        byte[] bArr = new byte[open.available()];        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;    }}
// fridavar fileOutputStream = Java.use("java.io.FileOutputStream");var a = 1;fileOutputStream.write.overload('[B').implementation = function (bArr) {    console.log("write: ");    if (a % 2) {        bArr = [100, 101, 120。。。。。]// 修复后的字节,也可也只替换头    }    a++;    var ret = fileOutputStream.write.overload('[B').call(this, bArr);    return ret;};

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

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[0], iArr[1], iArr[2], context),按提醒在 fix dex 而这里缺读取的 decode.dex,后面保留的又是 1.dex,那么间接把 1.dex 改个名字/data/user/0/com.zj.wuaipojie2024_2/app_data/decode.dex:

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.available()];        fileInputStream.read(bArr);        ByteBuffer wrap = ByteBuffer.wrap(bArr);        fileInputStream.close();        return wrap;    }    return null;}

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

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

查看修复胜利后的函数,很容易失去明码 048531267,也没啥用,还有个提醒:

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

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

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;};

而后运行,首先明码正确:

而后函数也修复进去了:

避免魔改被动调用一下:

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}:

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

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

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,微调一下,再缩放:

flag9{KHTALK}:

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 外面,没看懂轻易改了一个二进制就有了:

实际上应该是两个图层,默认是彩色的,flag4 是红色能够间接看,flag10 和背景一样导致显示不进去

间接在这个网站 https://www.georgeom.net/StegOnline/image,抉择 lnverse (RGBA) 甚至你还能够间接改后缀为 mp4:

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 整数倍数才行,不然简直不可能还原图片:

a = 942.5135817416999b = 1.0215884355337748c = 0.24768196677010001d = 224.16483995058888e = 2.9293942195858147f = 0.8924085229409133for 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.0000000000000671 20

flag12{HOXI} 很简略 wasm,批改一下 num 就行:

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

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

{"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 light person airplane motorcycle 轻易填按提醒批改就行,最初提交的内容:

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"]}