不能Hook的人生不值得 jsHook和模拟执行

简介: 不能Hook的人生不值得 jsHook和模拟执行

一、目标


李老板: 奋飞呀,上次分析的那个App 91fans.com.cn/post/bankda… 光能Debug还不够呀, 网页中的js也用不了Frida,我还想 Hook它的函数 ,咋搞呀? 再有App可以RPC去执行签名,这个js我如何去利用呀?总不能代码都改成js去做请求吧?


奋飞:老板呀,你一下提这么多要求,不是明摆着要我们加班吗?这次加班费可得加倍。


二、步骤

最简单易行的js Hook - console.log

98.png


我们的目的是Hook这个 encryptSm4ECB 函数,然后打印出它的入参和返回值。


在合适的位置下断点(一般是函数入口和出口)。然后在断点上点右键 -> 修改断点,然后在弹出的窗口里面输入要打印的变量。


TIP:  实际上这个功能是条件断点,可以在符合条件的时候触发断点,但是恰好可以用于打印变量值。修改成功之后断点图标会变颜色。

99.png


跑一下,我们想要的入参和结果都打印出来了。


TamperMonkey 注入

TamperMonkey 俗称油猴,你都可以理解他就是浏览器届的Frida,不过在这个样本里面我没有找到如何Hook 这个 encryptSm4ECB, 但使用它来Hook全局函数是可以成功的。有用油猴 Hook成功这个 encryptSm4ECB 的兄弟可以给我留言交流下。


Fiddler 插件注入

Fiddler抓包的同时是可以用插件来注入js代码的,这个看上去比较复杂,我也木有搞


Chrome启用本地替换

要是可以直接在这个 ArticleDetail.js 上去修改,增加打印变量的代码,岂不快哉。

Chrome其实提供了这个功能,算是文件级别的Hook,就是执行到 ArticleDetail.js 这个请求的时候,不向服务器发请求了,而是直接使用你本地替换的js。这样你就想怎么改就怎么改了。

100.png

源代码页 选择 替换,然后 勾选 启用本地替换,这时候浏览器会提示你给权限,然后选择一个本地的目录来存放要替换的js。101.png



回到 网络 页,选择你想替换的js,点右键 -> 保存并覆盖


再回到 源代码 页,找到这个js文件,实际它已经存到我们开始指定的目录下了。


这时候找到指定的函数位置写hook代码就可以了。


TIP: xxx.js 这种链接替换没问题,hook代码也能激活。 ArticleDetail.js?


v=ab4f0b37a4a90050d429 这种模式的js没有替换成功。原因未知,有成功的兄弟也留言交流下。


模拟执行第一步 先用 Nodejs 跑通


子曾经曰过:逆向是杂学,A-Z语言都要略懂点。js本来是跑在服务器端的,Nodejs一出,谁与争锋。


问下度娘和谷哥,把VSCode + NodeJs 搭配好,Hello World跑通,开干。


ArticleDetail.js 这个样本的代码还是很厚道的,基本木有混淆,一览无遗。


跑通代码的八字真言是 循序渐进,分而治之


一段一段代码,一个一个函数去跑通,你别一上来就把整段代码都复制上去,然后看着一堆报错就放弃治疗。

encryptSm4ECB: function(t) {
  var e = s("string" == typeof t ? t : JSON.stringify(t))
  ...
}


先执行这个e的值, e 调用了s这个函数,参数是t,但是判断了t是不是字符串,我们之前Hook的时候直接打印的就是  console.log(JSON.stringify(t));


所以这里的代码在 Nodejs里面可以写成:

var n = "dro";
var o = [20320, 25105, 20182, 30340, 22320, 30334, 21315, 19975, 20986, 20837, 19978, 19979, 21069, 21518, 25307, 38134, 22269, 26085, 26376, 23545, 38169, 22909, 22351];
function s(t) {
    var e, i, n = new Array;
    e = t.length;
    for (var r = 0; r < e; r++)
        (i = t.charCodeAt(r)) >= 65536 && i <= 1114111 ? (n.push(i >> 18 & 7 | 240),
            n.push(i >> 12 & 63 | 128),
            n.push(i >> 6 & 63 | 128),
            n.push(63 & i | 128)) : i >= 2048 && i <= 65535 ? (n.push(i >> 12 & 15 | 224),
                n.push(i >> 6 & 63 | 128),
                n.push(63 & i | 128)) : i >= 128 && i <= 2047 ? (n.push(i >> 6 & 31 | 192),
                    n.push(63 & i | 128)) : n.push(255 & i);
    return n
}
var t = '{"parentId":"f6be7358-f906-4087-b387-69cc17a9ebf8","parentType":"ARTICLE","pageIndex":1,"time":"2022-02-23T10:05:34.760","pageSize":5}';
var e = s(t);
console.log(e);


这里n、t、e的值都可以通过之前的hook方案打印出来。比对一下,e的值是ok的,说明s函数是可用的。

var encryptSm4ECB = function (t) {
    var e = s(t)
    , i = (new Date).getTime()
    , r = (i + "").split("")
    , o = [r[5], r[10]].join("")
    , c = s("CFKt03X9Ufk" + n + o);


这个c的值就有点复杂了,不过我们Hook的时候可以把n和o的值打印出来,那实际上调试的时候可以把c先写死,等价于

var cStr = 'CFKt03X9Ufkdro88';
var c = s(cStr);


TIP: 这里其实埋了一个坑,c的值和最后的时间戳timestamp是有关系的,要对应上。

在继续往下搞

var CMBSM4EncryptWithECB = function (t, e) {
        // if (!e || !t)
        //    return y.failed(c);
    // if ("object" != s(e) || "object" != s(t))
    //    return y.failed(F);
    // if (e.length <= 0)
    //    return y.failed(h);
    // if (16 != t.length)
    //    return y.failed(f);
    var i = encodeWithPKCS5(e, 16)
        , n = encryptWithECB(i, t);
    return n;
    // , r = new C;
    // return r.set("result", n),
    // y.success(r)
}


y这个类貌似就是为了输出错误提示,干脆不要它了。


返回值r就是把n封装了一下,感觉不够优雅,我们直接返回n吧。

var encryptWithECB = function (t, e) {
    // l(void 0 !== t && t.length % 16 == 0, "illegal plaintext:the length of plaintext must be the multiple of 16."),
    // l(void 0 !== e && 16 === e.length, "illegal key:the length of sm4Key must be 16 bytes.");
    for (var i = vt(e), n = t.length, r = new Array(n), a = 0; a < n;)
        bt(t, a, r, a, i, 0),
            a += 16;
    return r
}


这个l函数貌似也就是个错误提示,干掉它。


然后把依赖的 vtbt 等等函数都复制进来,貌似就能跑起来了,还有一个报错就是这个返回值。


由于我们直接返回了n所以要改改

var encryptSm4ECB = function (t) {
    var e = s(t)
    , i = (new Date).getTime()
    , r = (i + "").split("")
    , o = [r[5], r[10]].join("")
    , c = s("CFKt03X9Ufk" + n + o);
    // var cStr = 'CFKt03X9Ufkdro88';
    // var c = s(cStr);
    try {
        var l = CMBSM4EncryptWithECB(c, e);
        for (var u = "", h = 0; h < l.length; h++)
            u += String.fromCharCode(l[h]);
        console.log(i);   
        return base64encode(u);    
        /*    
        return {
                data: window.btoa(u),
            timestamp: i
        }
        // */
    } catch (d) { }
    return t instanceof Object ? null : ""
}


这里被这个window.btoa给坑了,问了一下谷哥,哥说这是浏览器提供的Base64转码。


NodeJs也提供一个Base64函数,但是转出来不一样……


幸好谷哥还是靠谱的,找了个js写的Base64

var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));
var base64encode = function (e) {
    var r, a, c, h, o, t;
    for (c = e.length, a = 0, r = ''; a < c;) {
        if (h = 255 & e.charCodeAt(a++), a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4),
                r += '==';
            break
        }
        if (o = e.charCodeAt(a++), a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
                r += base64EncodeChars.charAt((15 & o) << 2),
                r += '=';
            break
        }
        t = e.charCodeAt(a++),
            r += base64EncodeChars.charAt(h >> 2),
            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
            r += base64EncodeChars.charAt(63 & t)
    }
    return r
}


比对了一下,一级棒,和Chrome Hook出来的结果一致。


那如何利用这个结果呢?可以用NodeJs启动一个web服务器,然后rpc来执行。


下面我们再介绍一个优雅的方法,直接用python来执行js


Js模拟库介绍

江湖上有很多Python写的JavaScript执行引擎。


PyV8


pypi.org/project/PyV…


据说年老失修,最新的版本是2010年的,大佬们不推荐使用。


但是实际上2013年它还更新了一般,廉颇老矣,尚能饭否?我觉得就冲V8这个名字,就值得试试。


Js2Py


github.com/PiotrDabkow…

同样嫌它年纪大了,实际上人家5个月前有更新,不能小看大龄程序员的潜力。


PyExecJS


pypi.org/project/PyE…


一个最开始诞生于 Ruby 中的库,后来被移植到了 Python 上。


比较活跃,最新的更新是2018年,江湖上有很多它的使用例子。很多人建议使用


PyminiRacer


github.com/sqreen/PyMi…


作者号称这是一个继任 PyExecJS 的库,比较新,这玩意看缘分,飞哥第一次就搜到了它,

所以今天就用它了。


Pyppeteer


github.com/pyppeteer/p…


这个也可以试试,其实很多被人嫌弃年纪大的库,都还在努力更新呢。


Selenium

www.selenium.dev/

  • 一个 web 自动化测试框架,可以驱动各种浏览器进行模拟人工操作
  • 用于渲染页面以方便提取数据或过验证码
  • 也可以直接驱动浏览器执行 JS


Selenium可以驱使浏览器,那么执行个js就不在话下了,这个做最后的杀手锏用。


PyminiRacer模拟执行encryptSm4ECB


先来个Hello World

from py_mini_racer import py_mini_racer
jsSource = '''
var ffdemo = function(str){
  return str;
}
'''
ctx = py_mini_racer.MiniRacer()
ctx.eval(jsSource)
print(ctx.call("ffdemo", "Hello World"))


是的,就是这么帅,3行代码搞定。


依葫芦画瓢,把刚才NodeJs跑通的代码复制进去,执行 print(ctx.call("encryptSm4ECB", strFF))

结果就出来了。


三、总结


NodeJs去执行的之后,不要一开始就把整页代码都拷贝上去,要分而治之,一个一个函数跑通。


JavaScript保护只有一条路可以走了,那就是混淆。下次找到合适的样本我们再一起分析下。102.png


廉颇老矣,尚一饭斗米,肉十斤,生命不止,coding不息。


TIP: 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我wx: fenfei331 讨论下。


关注微信公众号: 奋飞安全,最新技术干货实时推送

相关文章
|
SQL 存储 关系型数据库
一文搞懂SQL优化——如何高效添加数据
**SQL优化关键点:** 1. **批量插入**提高效率,一次性建议不超过500条。 2. **手动事务**减少开销,多条插入语句用一个事务。 3. **主键顺序插入**避免页分裂,提升性能。 4. **使用`LOAD DATA INFILE`**大批量导入快速。 5. **避免主键乱序**,减少不必要的磁盘操作。 6. **选择合适主键类型**,避免UUID或长主键导致的性能问题。 7. **避免主键修改**,保持索引稳定。 这些技巧能优化数据库操作,提升系统性能。
1110 4
一文搞懂SQL优化——如何高效添加数据
|
Java API 调度
Android系统 自定义开机广播,禁止后台服务,运行手动安装应用接收开机广播
Android系统 自定义开机广播,禁止后台服务,运行手动安装应用接收开机广播
1333 0
|
10月前
|
人工智能 API 语音技术
VideoCaptioner:北大推出视频字幕处理神器,AI自动生成+断句+翻译,1小时工作量5分钟搞定
VideoCaptioner 是一款基于大语言模型的智能视频字幕处理工具,支持语音识别、字幕断句、优化、翻译全流程处理,并提供多种字幕样式和格式导出。
1795 89
VideoCaptioner:北大推出视频字幕处理神器,AI自动生成+断句+翻译,1小时工作量5分钟搞定
|
12月前
|
存储 缓存 物联网
MNN推理框架将大模型放进移动端设备,并达到SOTA推理性能!
随着移动端(手机/平板等)算力、内存、磁盘空间的不断增长,在移动端部署大模型逐渐成为可能。在端侧运行大模型,可以有一系列好处:去除网络延迟,加快响应速度;降低算力成本,便于大规模应用;不需数据上传,保护用户稳私。
2077 13
MNN推理框架将大模型放进移动端设备,并达到SOTA推理性能!
|
机器学习/深度学习 数据采集 TensorFlow
使用Python实现智能食品消费偏好分析的深度学习模型
使用Python实现智能食品消费偏好分析的深度学习模型
244 8
|
12月前
|
API 数据库
京东图片搜索商品拍立淘接口(JD.item_search_img)
拍立淘是阿里巴巴淘宝平台推出的基于图像识别技术的购物应用功能,旨在提升商品搜索效率与准确性。用户可通过上传图片快速找到相似商品。其核心接口item_search_img利用先进图像识别技术提取商品特征,并在数据库中匹配相似商品,返回包含商品ID、标题、价格等详细信息的结果列表,支持按价格、销量等多种方式排序,极大优化了用户的购物体验。
|
11月前
|
API Python
京东拍立淘图片搜索商品接口系列(京东 API)
简介:本文介绍了如何使用拍立淘图片搜索 API 在京东平台上查找相似商品。首先需安装 Python 库 `requests`,并通过内置库 `hashlib` 生成签名。API 支持通过图片 URL 或 Base64 编码的图片进行搜索,返回商品名称、价格等信息。示例代码展示了如何构建请求并处理响应。应用场景包括电商购物助手和竞品分析,帮助用户和商家提高购物效率和市场竞争力。
|
机器学习/深度学习 存储 人工智能
【大语言模型】ACL2024论文-01 Quantized Side Tuning: Fast and Memory-Efficient Tuning of Quantized Large Language
本文介绍了Quantized Side Tuning(QST)方法,旨在解决大型语言模型(LLMs)微调过程中的内存效率和速度问题。QST通过将模型权重量化为4位,并引入一个与LLM分离的侧网络,显著减少了内存占用并加快了微调速度,同时保持了与现有技术相当的性能。实验表明,QST可以将总内存占用减少高达2.3倍,并将微调速度提高高达3倍。
|
机器学习/深度学习 存储 算法
阿里云国际站:拍立淘-以图搜图中的图像搜索算法是怎么样的?
@luotuoemo飞机@TG 阿里云国际站:拍立淘-以图搜图中的图像搜索算法是怎么样的?图像搜索在现代搜索系统中扮演了重要角色,尤其在电子商务网站如阿里巴巴等,它更是一个必不可少的功能。拍立淘是阿里云国际站的一个以图搜图功能,它使用了复杂的图像搜索算法进行图片匹配和识别。以下是对该算法的简单描述。
|
SQL 开发框架 关系型数据库
基于SqlSugar的数据库访问处理的封装,支持多数据库并使之适应于实际业务开发中
基于SqlSugar的数据库访问处理的封装,支持多数据库并使之适应于实际业务开发中