逆向目标

  • 目标:天翼云登录框
  • 链接:https://m.ctyun.cn/wap/main/auth/login
  • 简介:主要对加密的函数的提取,找打加密的地方,分析加密函数,提取重要的JS代码进行加密算法还原

image.png

  • 发现这块的JS是webpack打包过的

image.png

webpack的特点:将所有的函数封装好,集成到一个个模块中,然后使用加载器或方法体来调用这些模块,变成一个对象,这样子就可以使用这个模块里面的函数和属性。

image-20240507154149907

webpack讲解

js逆向——webpack扣法_webpack 逆向-CSDN博客

01 webpack简介

webpack是个静态模块打包工具,目的是为了让前端工程师写的前端代码变成浏览器可以识别的代码,并且可以达到前端项目的模块化,也就是如何更高效地管理和维护项目中的每一个资源。

但无疑是对js逆向造成了妨碍。但如果掌握了扣取webpack的一些技巧也是不难的。接下来,说一下对扣取webpack代码的一些理解。

02 webpack的组成

一个最基本的webpack组成有两个部分:

  • 自执行函数
  • 模块加载器(实际上也就是一个函数而已)

img

//1.传入的参数是一个数组
var _xxx;
function(x){
function xxx(y){ #这个是模块加载器,这个y传入的可以是索引和对象的key,参数表示的是列表或对象的形参
x[y].call(参数)
}
_xxx = xxx;
}( [function(){console.log("第一个")},
function(){consolelog("第二个")},
function(){console.log("第三个")}
] );

//比如
_xxx(0); //执行得到的就是输出——第一个
_xxx(1);//执行得到的就是输出——第二个


//2.传入的参数是一个对象
var _xxx;
function(x){
function xxx(y){ #这个是模块加载器,这个y传入的可以是索引和对象的key,参数表示的是列表或对象的形参
x[y].call(参数)
}
_xxx = xxx;
}( {
obj1:function(){console.log("第一个")},
obj2:function(){consolelog("第二个")},
obj3:function(){console.log("第三个")}
} );
//因为传入的是一个对象,那想要调用对象就要指明key
_xxx("obj1")
_xxx("obj2")//和数组相比只是调用方式不同而已,原理基本一致

03 实战看webpack组成

光说不练假把式,接下来我们从真实的网站中看一看关于webpack组成:

img

通过观察真实网站的webpack可以发现,这种情况与我们自己写的那个大差不差,无非就是加载器函数中功能更完善了些。

这种加载器函数与执行对象在一个文件中的webpack很好扣js,至于如何扣我们以后再说,今天要解决的是下面的这种要怎么扣取代码呢?

img

就像图中所展示的那样,要怎么才能把目标给扣出来呢?

首先,想要扣取webpack代码就要先找到其基本要素,注意:自执行函数不是关键,关键的是传入的实参,也就是那个对象(数组)

  • 传入的实参
    • 传入的实参也就是我们的目标,把目标函数放到自己构建的对象里面就行了
//构建自己的对象,把上面的目标给填到这个里面就行
{
"wk8/": function(e, t, n) {
"use strict";
n.d(t, "g", function() {
n.d(t, "q", function() {
n.d(t, "m", function() {
n.d(t, "I", function() {
n.d(t, "o", function() {
n.d(t, "r", function() {
n.d(t, "v", function() {
n.d(t, "n", function() {
var o = n("zvER")
, a = n("MuMZ");
function r(e, t, n) {
var r = {
username: e,
password: Object(a.a)(t),
captcha: n
};
return o.a.formPost("/user/login", r)
}
function i(e) {
function m(e) {
function g(e, t) {
function y(e) {
function L(e) {
},
"test":function(){console.log("test success!!")} //这个仅用于测试使用
}
    • 当目标函数中有依赖的模块时,把依赖的模块也通通放到自己写的对象里面即可
  • 加载器函数
    • 当看到如上图的代码时可能会有人会不明白这哪里有加载器,就连自执行函数都没有
    • 但实际上肯定是有加载器的,如图

img

    • 所以在定位到目标时,目标的大函数一般会加载其他模块,若要加载其他模块就会使用到加载器,以此就可以找到加载器了
  • 但是,当断到我们想要的[目标函数]时再去定位n(“”)函数哪里是显示不出来的,如图

image-20240507160832661

这是因为加载器函数实际上是在你打开网页的时候就已经加载到内存中了,而只有当我们进行登录这一具体操作时才会执行调用这个函数,所以这时是找不到加载器的。

解决方法也很简单: 只需在加载模块的前面加上断点,然后刷新页面就可以找到加载器的位置了

img

  • 到此,目标函数以及加载器都已经找齐了

04 webpack调用原理分析

在上面的环节中我们确定了目标函数并找到了加载器,但正如我们看到的那样,它并不像我们自己写的那个webpack直接把大对象给传进去了,而是在另一个独立的文件里面,那么加载器又是如何对另一份独立的文件中的模块进行加载的呢?

首先来看我们找的的那个加载器,如图:

img

接下来我们要解决的是自执行函数的形参c是如何变成独立文件中的那个存有模块的对象的:

  1. 我们把得到的自执行函数拿出来放到node.js环境中执行一下
    • 其中将传入的空数组变成一个对象,对象里面放有一个测试函数
    • 输出自执行函数的形参
    • 并将加载器函数给导出来

img

  1. 我们将目标函数所在的那个独立文件全部写入到另一个文件当中去

img

  1. 在自执行函数的上面对刚刚写入的文件进行引入,再次执行函数

img

  1. 通过实验,我们知道了通过引入外部文件可以将模块加载到形参里面,那么是如何实现的呢? 通过调试自执行代码可以明白
  • 在与单个文件有相同部分下面可疑部分打上几个断点如下:

img

  • 接着向下进行,通过读代码可以知道通过for循环执行了函数n(),通过下图可以知道是为了加载外部模块

img

  • 再向下进行到第三个断点

img

至于那个for循环是怎么将外部模块进行引入的我们并不需要深究,只需要知道当扣代码时一定不能将这个for循环给忘掉,如果忘掉的话外部文件是无法加载进来的。

逆向参数

1. 定位到password加密函数

img

encodeURI(Object(w["c"])(a.value, Object(w["f"])(Object(w["g"])(r.value))))

拆分一下后如下:

r.value
Object(w["g"])(r.value)
Object(w["f"])(Object(w["g"])(r.value)

a.value

Object(w["c"])(a.value, Object(w["f"])(Object(w["g"])(r.value)))

encodeURI(Object(w["c"])(a.value, Object(w["f"])(Object(w["g"])(r.value))))

img

2. 第一个参数分析

a.value:直接就是输入的密码明文

img

3. 第二个参数分析

Object(w[“f”])(Object(w[“g”])(r.value)

(1)w[“g”]

这个函数是js的[字符串替换]函数,这个函数有两个参数,第一个函数可以是字符串或正则表达式(用以进行匹配的子字符串),第二个函数是字符串(替换匹配的子字符串)。就是在原字符串上把和第一个参数相同的字符串替换成第二个参数的字符串。

img

这个例子使用的是正则表达式匹配字符串,其中”/ /“这个是固定写法,”\s”是转移符号用以匹配任何空白字符,包括空格、制表符、换页符等等,“g”表示全局匹配将替换所有匹配的子串,如果不加”g”当匹配到第一个后就结束了。

这个例子的意思就是将原字符串中的所有空白字符替换成””,比如”abc d efg “字样的字符串使用这个函数后将变成”abcdefg”。

(2)w[“f”]

这个函数的作用就是用户名填充。

当输出的用户名不足24位的时候,在后面补充 “0”。比如输入用户名为 “admin@123.com“,则填充后为 “admin@123.com00000000000

img

img

4. 加密函数分析

img

1)根据代码可以初步判断为DES加密:

img

分析代码逻辑:

  • 只传入了一个参数:密码字段
  • 前面都是var定义局部变量
  • 关键加密代码:
l = p.a.TripleDES.encrypt(e, d, s);

这块相当于对象一步步调用方法,所以,这块要先定位到对象 “p”

2)向上找,定位到对象p

l = t("3452")
p = t.n(l)

img

这块可以明显看到就是相当于webpack的方式,模块化调用

3)将当前的整个代码复制下来放到notepad++中

img

img

4)找到webpack代码的分发器(加载器)

刷新页面,在刚刚打的断点p处停下
l = t(“3452”) 中加载了用的加解密算法,我们,需要的就是DES的加密和解密算法

img

所以,接下来定位到 “t“ ,t就是我们要找的分发器:

img

简单改写一下分发器:

var fff

!function(e){
var r = {}
function u(n) {
if (r[n])
return r[n].exports;
console.log('-->',n)
var t = r[n] = {
i: n,
l: !1,
exports: {}
};
return e[n].call(t.exports, t, t.exports, u),
t.l = !0,
t.exports
}
fff = u
}({
//模块化代码填充
})

5)加密模块为”3452”,但是当前扣取到notepad++中的代码没有 3452 模块

img

全局查找,定位到 “3452” 模块代码:

img

6)扣取加密模块代码

img

将模块代码扣取出来。放到刚刚其前一步做好的加载器中:

从"00bb"开始,扣取全部模块代码

省略。。。

img

7)编写加密函数

//构建加密函数
var p = fff('3452'); //定义导出函数,将加密模块导出为全局变量p

function getUsername(e){
//用户名长度不够末尾自动补零
var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
if (e && "string" === typeof e) {
var t = n.text || "0",
r = n.length || 24;
if (e.length < r)
for (var a = e.length; a < r; a++)
e += t;
else
e = e.substring(0, r);
//去除用户名中的空格
return e.replace(/\s+/g, "")
}
}

function getPwd(pwd,name){
//构建加密函数

//导出加密p对象
var n = getUsername(name)
, c = pwd
, d = p.enc['utf-8'].parse(n)
, l = {
mode: p.mode['ECB'],
padding: p.pad['pkcs7']
}
, s = p.DES.encrypt(c, d, l);
return s.toString()
}

8)汇总一下代码

参考后面第四部分,完整代码

分发器代码+加密模块代码+自写加密函数代码

9)测试

img

代码编写

1. 第二个参数代码

作用:处理用户名(使用正则替换用户名中的任何空白字符【包括空格、制表符、换页符等等】为空,然后将用户名的长度使用 “0” 填充为 24位)

Q = function(e) {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
return e.replace(/\s+/g, "")
};

F = function(e) {

var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
if (e && "string" === typeof e) {
var t = n.text || "0",
r = n.length || 24;
if (e.length < r) for (var a = e.length; a < r; a++)
e += t;
else e = e.substring(0, r);
return e
}
}

img

2. 完整JS代码

代码太多了,插入附件参考

hexo/202405/m.ctyun.cn.js · 要优秀/img - Gitee.com