猿人学第二届-第一题 逆向分析详解

猿人学第二届-第一题 逆向分析详解

一、抓包分析

API接口

https://match2023.yuanrenxue.cn/api/match2023/1

关键参数

  • token - 计算出的密钥
  • now - 时间戳
  • page - 页码

初步推测

根据参数特征,推测加密方式可能为 now + page 进行 AES 编码。

二、逆向过程

1. 定位token生成位置

要找到 token 的计算位置,只需找到接口的请求位置,查看请求体内的参数赋值过程即可定位到加密函数。这里采用最简单的方式:在开发者工具中找到接口请求,查看启动器位置(也就是请求走过的所有堆栈)快速定位发送位置。

在这里插入图片描述
在这里插入图片描述


2. 关键代码分析

g = '666yuanrenxue66' // AES密钥
// e = 时间戳, d = 页码, g = AES密钥
h = f['AES']['encrypt'](e + String(d), g, {
    'mode': f['mode']['ECB'],
    'padding': f['pad']['Pkcs7']
})

'token': f['MD5'](h['toString']())['toString']()

这是一个 AES-ECB 加密方式,加密内容为时间戳 + 页码,密钥为 g。但这里的 MD5 调用与正常的函数调用不一样,需要查看 f 指向的是什么。

3. 模块加载器分析

function a(b, c, d) {
    function f(j, k) {
        if (!c[j]) {
            if (!b[j]) {
                var l = 'function' == typeof require && require;
                if (!k && l)
                    return l(j, !0x0);
                if (g)
                    return g(j, !0x0);
                var m = new Error('Cannot\x20find\x20module\x20\x27' + j + '\x27');
                throw m['code'] = 'MODULE_NOT_FOUND', m;
            }
            var q = c[j] = {
                'exports': {}
            };
            b[j][0x0]['call'](q['exports'], function(s) {
                var v = b[j][0x1][s];
                return f(v || s);
            }, q, q['exports'], a, b, c, d);
        }
        return c[j]['exports'];
    }
    for (var g = 'function' == typeof require && require, h = 0x0; h < d['length']; h++)
        f(d[h]);
    return f;
}

f = a('crypto-js')

f 获取了 a 内的 crypto-js 模块,而 a 是一个自制的模块加载器。可以理解为 f 等同于 CryptoJS = require('crypto-js')

4. 魔改加密函数陷阱

网站使用自定义模块加载器而不直接用 require('crypto-js'),这里存在一个坑:魔改加密函数

网站将原生加密函数库代码写到本地,修改其中的逻辑后调用,导致使用未魔改的导入库与已魔改的出参不一致。

可以用以下代码验证:

const plaintext = e + String(d); 
const key = g;

const h = CryptoJS.AES.encrypt(plaintext, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
})

三、补环境方案

方案选择

有两种方式:

  1. 将函数找齐,保存到本地重命名后调用
  2. 直接补环境,将 a 导出到全局变量中,在外部调用

我们采用方式二:补环境

1. 复制代码并处理报错

将代码全文复制到本地 JS 中运行,发现报错在发送网络请求的地方。因为我们不需要发送网络请求,将此块注释掉即可。

在这里插入图片描述
在这里插入图片描述

2. 导出模块到全局变量

let cyjs // 定义全局变量

(function() {
    function a(b, c, d) {
        function f(j, k) {
            if (!c[j]) {
                if (!b[j]) {
                    var l = 'function' == typeof require && require;
                    if (!k && l)
                        return l(j, !0x0);
                    if (g)
                        return g(j, !0x0);
                    var m = new Error('Cannot\x20find\x20module\x20\x27' + j + '\x27');
                    throw m['code'] = 'MODULE_NOT_FOUND', m;
                }
                var q = c[j] = {
                    'exports': {}
                };
                b[j][0x0]['call'](q['exports'], function(s) {
                    var v = b[j][0x1][s];
                    return f(v || s);
                }, q, q['exports'], a, b, c, d);
            }
            return c[j]['exports'];
        }
        for (var g = 'function' == typeof require && require, h = 0x0; h < d['length']; h++)
            f(d[h]);
        return f;
    }
    return a;
}()({
    0x1: [function(a, b, c) {}, {}],
    0x2: [function(a, b, c) {
        cyjs = a // 导出模块
        console.log("模块获取", cyjs)
        // ...
    }
})

3. 编写测试逻辑

time = '1765086350541'
page = '1'
sign = '666yuanrenxue66'
f = cyjs('crypto-js')

h = f['AES']['encrypt'](time + String(page), sign, {
    'mode': f['mode']['ECB'],
    'padding': f['pad']['Pkcs7']
})
token = f['MD5'](h['toString']())['toString']()

if(token === "2a1de47d1e0d001c595ab047ddd3c3cb"){
    console.log("成功:", token)
} else {
    console.log("失败", token)
}

输出:

模块获取 [Function (anonymous)]
失败 9333a5a18a763c9955653eccdcd8b166

参数保持一致,但计算出来的数据还是不一样。这就需要进入补环境的核心:针对环境检测进行针对性补入,模拟真实浏览器。

4. 补入三件套

window = global
Window = function Window(){}
Object.setPrototypeOf(window, Window.prototype)
delete global;

document = {}
Document = function Document(){}
Object.setPrototypeOf(document, Document.prototype)

navigator = {}
Navigator = function Navigator(){}
Object.setPrototypeOf(navigator, Navigator.prototype)

location = {}
Location = function Location(){}
Object.setPrototypeOf(location, Location.prototype)

运行后发现加密参数变动了,说明补的环境是有作用的。接下来需要接路由,查看函数中到底获取了哪些属性。

5. 路由函数监控

function watch(obj, obj_name) {
    if (obj === undefined || obj === null) {
        console.log(`警告: "${obj_name}" 是 ${obj === null ? 'null' : 'undefined'},跳过代理`);
        return obj;
    }
    if (typeof obj !== 'object' && typeof obj !== 'function') {
        console.log(`警告: "${obj_name}" 不是对象或函数类型,而是 ${typeof obj},跳过代理`);
        return obj;
    }

    const handler = {
        get(target, property, receiver) {
            if (property === 'toJSON') {
                return target[property];
            }
            console.log(
                `方法: get | 对象: "${obj_name}" | 属性: ${typeof property === 'symbol' ? property.toString() : property} | 属性类型: ${typeof property} | 属性值类型: ${typeof target[property]}`
            );
            return Reflect.get(target, property, receiver);
        },
        // ... 其他 handler 方法
    };

    return new Proxy(obj, handler);
}

location = watch(location, 'location')

输出:

方法: getPrototypeOf | 对象: "navigator" | 获取原型链
方法: get | 对象: "location" | 属性: href | 属性类型: string | 属性值类型: undefined
失败 932497bfb68d0ba03c0b071493e0e9a6

6. 补入 location.href

在浏览器控制台查看真实环境:

location.href
// 'https://match2023.yuanrenxue.cn/topic/1'

补入这个值:

location = {
    'href': 'https://match2023.yuanrenxue.cn/topic/1'
}
Location = function Location(){}
Object.setPrototypeOf(location, Location.prototype)
location = watch(location, 'location')

输出:

方法: get | 对象: "location" | 属性: href | 属性类型: string | 属性值类型: string
失败 06ada6373be1f4b15da20f9fbb239d01

补入 href 后值发生了极大变化,说明环境补对了,但密钥依然错误。此时已经没有其他调用了,需要去读代码,而不是盲目添加更多浏览器对象。

7. 发现可疑代码

for (var B = 0x0; B < z; B++) {
    if (B < x)
        A[B] = w[B];
    else {
        u = A[B - 0x1];
        if (!(B % x))
            u = u << 0x8 | u >>> 0x18,
            u = i[u >>> 0x18] << 0x18 | i[u >>> 0x10 & 0xff] << 0x10 | i[u >>> 0x8 & 0xff] << 0x8 | i[u & 0xff],
            u ^= s[B / x | 0x0] << 0x18;
        else
            x > 0x6 && B % x == 0x4 && (delete window,
            window = 0x0,
            u = window ? i[u >>> 0x1a] << 0x18 | i[u >>> 0x10 & 0xff] << 0x10 | i[u >>> 0x8 & 0xff] << 0x8 | i[u & 0xff] : i[u >>> 0x16] << 0x18 | i[u >>> 0x10 & 0xff] << 0x10 | i[u >>> 0x8 & 0xff] << 0x8 | i[u & 0xff]);
        A[B] = A[B - x] ^ u;
    }
}

这里的 delete window, window = 0x0 在浏览器中是无效的,所以不会导致 window = 0。据此推理,当 window = 1 时,我们的代码与浏览器流程相同。

8. 修改代码

for (var B = 0x0; B < z; B++) {
    if (B < x)
        A[B] = w[B];
    else {
        u = A[B - 0x1];
        if (!(B % x))
            u = u << 0x8 | u >>> 0x18,
            u = i[u >>> 0x18] << 0x18 | i[u >>> 0x10 & 0xff] << 0x10 | i[u >>> 0x8 & 0xff] << 0x8 | i[u & 0xff],
            u ^= s[B / x | 0x0] << 0x18;
        else
            x > 0x6 && B % x == 0x4 && (
            u = 1 ? i[u >>> 0x1a] << 0x18 | i[u >>> 0x10 & 0xff] << 0x10 | i[u >>> 0x8 & 0xff] << 0x8 | i[u & 0xff] : i[u >>> 0x16] << 0x18 | i[u >>> 0x10 & 0xff] << 0x10 | i[u >>> 0x8 & 0xff] << 0x8 | i[u & 0xff]);
        A[B] = A[B - x] ^ u;
    }
}

删除 delete window, window = 0x0,将值固定为 1。

输出:

模块获取 [Function (anonymous)]
成功: 2a1de47d1e0d001c595ab047ddd3c3cb

此时我们的值与浏览器出值一致,成功还原出请求 token。

四、总结

这道题主要考验读代码的能力。代码无强混淆,逻辑基本都能看通。关键点在于:

  1. 理解自定义模块加载器的作用
  2. 认识到魔改加密函数的存在
  3. 正确补入浏览器环境
  4. 通过路由函数监控环境调用
  5. 仔细阅读代码找出环境检测逻辑

整个逆向过程需要耐心分析,不能盲目补环境,要结合实际代码逻辑进行针对性处理。

03-26
### 逆向工程与反编译概述 逆向工程是一种通过对软件的目标代码进行分析,将其转化为更高级别的表示形式的过程。这一过程通常用于研究现有系统的内部结构、功能以及实现细节。在Java和Android领域,反编译工具被广泛应用于逆向工程中。 #### Java逆向工程中的Jad反编译工具 Jad是一款经典的Java反编译工具,能够将`.class`字节码文件转换为可读的`.java`源代码[^1]。虽然它可能无法完全恢复原始源代码,但它提供了足够的信息来帮助开发者理解已编译的Java程序逻辑。Jad支持多种反编译模式,并允许用户自定义规则以适应不同的需求。此外,其命令行接口和图形界面使得复杂代码的分析变得更加便捷。 #### Android逆向工程中的JEB反编译工具 针对Android应用的逆向工程,JEB是由PNF Software开发的一款专业级工具[^2]。相较于其他同类产品,JEB不仅具备强大的APK文件反编译能力,还能对Dalvik字节码执行高效而精准的操作。它的核心优势在于以下几个方面: - **广泛的平台兼容性**:除Android外,还支持ARM、MIPS等多种架构的二进制文件反汇编。 - **混淆代码解析**:内置模块能有效应对高度混淆的代码,提供分层重构机制以便于深入分析- **API集成支持**:允许通过编写Python或Java脚本来扩展功能并完成特定任务。 #### APK反编译流程及其意义 当涉及到具体的APK包时,可以通过一系列步骤提取其中的信息来进行全面的安全评估或者习目的的研究工作[^3]。这些步骤一般包括但不限于获取资产目录(`assets`)内的资源数据;解密XML配置文档如`AndroidManifest.xml`定位应用程序启动点;最后利用上述提到的各种专用软件重现整个项目框架供进一步探讨。 ```bash # 使用apktool反编译APK示例 apktool d your_app.apk -o output_directory/ ``` 以上命令展示了如何借助开源工具ApkTool轻松拆卸目标安卓档案至易于探索的状态下。 ### 结论 无论是传统的桌面端还是现代移动端环境里头,恰当运用合适的反编译解决方案都是达成逆向工程项目成功不可或缺的一环。每种工具有各自专精之处,在实际应用场景当中应当依据具体需求做出明智的选择。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值