F5 Shape最新版逆向分析-加解密和补环境
2024-6-6 17:52:10 Author: mp.weixin.qq.com(查看原文) 阅读量:31 收藏


前言

F5 Networks 收购了Shape Security又叫F5 shape,谷歌可以找到该公司相关资料。很多国外站都使用了该公司的产品作为登录接口的反机器人方案。
比如:美西南、xbk等,下文称之为Shape。
url: aHR0cHM6Ly93d3cuc291dGh3ZXN0LmNvbS8=
Shape是应该是jsvmp的业界天花板了。自定义指令集有200多个。业务代码都编译成了code流。其中指令顺序,js虚拟机函数调用栈,变量存储都是随机的,即使原始收集浏览器信息的函数闭包的调用顺序也会被编译器随机位置。
早在3年前,本人就研究过这个东西,当时没有任何参考资料。纯靠一股子蛮力分析清楚了加解密相关算法。最近看到了一些讨论Shape的帖子,激起了兴趣,又分析下最新版的Shape,有一点点经验,分享出来供大家参考。JS逆向和安全防护de研究交流可以私聊我。


jsvmp

官方资料或者chatgpt的回答,我就懒得抄了,读者可以自行搜索。只说下个人的理解,还有分析jsvmp的大概思路。
PC端的vmp。鼎鼎大名,读者应该都用过或者听过。所谓vmp就是把简单的几行代码,通过自定义指令的方式,把代码膨胀几百倍,让逆向者找不到头绪。
举个简单例子:
C++:
void funcAdd(a, b){
return a+b;
}

asm:
.text:0000000140001000 sub_140001000 proc near ; CODE XREF: sub_140001020+E↓p
.text:0000000140001000 ; sub_140001020+21↓p
.text:0000000140001000
.text:0000000140001000 arg_0 = dword ptr 8
.text:0000000140001000 arg_8 = dword ptr 10h
.text:0000000140001000
.text:0000000140001000 mov [rsp+arg_8], edx
.text:0000000140001004 mov [rsp+arg_0], ecx
.text:0000000140001008 mov eax, [rsp+arg_8]
.text:000000014000100C mov ecx, [rsp+arg_0]
.text:0000000140001010 add ecx, eax
.text:0000000140001012 mov eax, ecx
.text:0000000140001014 retn
.text:0000000140001014 sub_140001000 endp

vmp:
自己搞一个编译器,把汇编指令转为自定义指令,比如把+这个指令对应的add汇编指令搞成一个函数,然后里面各种弯弯绕。
自己维护一个函数的调用栈,变量栈。
上面几行汇编就在vmp这个虚拟机里跑成千上万条,那对逆向分析的人来说,就很难搞清楚这个成千上万到底跑了个啥。

jsvmp:
与PC端vmp类似,也是把简单的指令复杂化,具体到Shape的+指令就对应于好几个函数
, function(c) {
c.x[c.x.length - 2] = c.x[c.x.length - 2] + c.x[c.x.length - 1]; //c.x就是局部变量栈
c.x.length -= 1
}
, function(c) {
var E = j[c.W];
var F = j[c.W + 1];
c.W += 2;
var T = c.y.c(E);
var a = T[F];
var i = c.x[c.x.length - 1];
c.x[c.x.length - 1] = i + a
}
其他各种运算符,比如- & | > < == ! 等等都是如此,对应于一个函数。这样做的目的就是可以自行维护一个函数调用栈和变量栈

所以Shape这个jsvmp也是一个所谓的栈式虚拟机。
其实当下前端安全领域,为了隐藏加解密核心代码或者隐藏收集了哪些浏览器信息,VM的种类和实现已经成千上万了。经常分析这类型的人应该是对此一点都不陌生了。


处理jsvmp的思路

一般朴素的处理思路,当然是想办法还原原始代码,这个也确实应该是一劳永逸的终极解决办法。比如AST语法树还原。我也见过使用该方案处理简单VM的实例。但是对于稍微复杂点的VM,这个办法要么工作量很大,要么连理论上的可能性都没有了。对于Shape,我的认知来说,还原原始代码应该是绝无可能。
回过头来讲,我们一般只需要分析清楚这段VM执行了什么就行了,所以不还原也是无所谓的。那要搞清楚VM执行了什么,最好的办法就是插装打日志了。输出所有操作到console控制台。通过分析日志搞清楚VM执行了什么。
再回过头来说,有时候我们都不需要知道这段VM执行了什么。我们只需要调用这段VM跑我们需要的结果就行了。那对于纯算法的VM直接跑就行了。对于收集浏览器信息的vm,就要使用补环境大法了。


实战Shape

1.找到shape在哪

chrome浏览器,F12打开开发者工具,打开美西南url,Login登录随便输入账号密码进行登录,触发一个请求,看抓包,请求头里Ee30zvqlwf-A,Ee30zvqlwf-B,Ee30zvqlwf-C,Ee30zvqlwf-D,Ee30zvqlwf-F,Ee30zvqlwf-Z就是Shape加密得到的参数了,不带这几个参数请求是无法成功取到预期结果的。
查看启动器也就是请求的调用堆栈即可找到Shape所在的js。
Shape的特征就是带有如下js。

2.分析Shape执行了什么

插装打日志大法配合F8 F10 F11单步调试,当你差不多按坏了一个键盘,投入至少个把月的时间精力,大概能搞清楚执行了什么。
这里直接说答案:收集浏览器信息,然后加密成上面请求里的各个参数。收集的浏览器信息多到只有你想不到的,没有他们不采集的。
美西南收集了66个浏览器相关的信息。
(obj.member=value) -> (66) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, Array(2),
{…}, {…}, {…}, {…}, {…}, {…}, Array(2), {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…},
{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] 34 (2) [Array(0), Array(13)]
这里简单拿收集信息里的66份之一做个演示。

成员29收集浏览器基础信息

下面是收集信息的明文结果(这里贴的是我直接通过Ee30zvqlwf-A参数,反向解密取到的,因为Shape是一边序列化一边加密的,所以是断不到明文瞬间的。我这里用反向解密拿到)
解密成员:29
Binary size: 308
35 2E 30 20 28 57 69 6E 64 6F 77 73 20 4E 54 20 53 46 48 32 40 87 105 110 100 111 119 115 32 78 84 32 5.0.(Windows.NT.
31 30 2E 30 3B 20 57 69 6E 36 34 3B 20 78 36 34 49 48 46 48 59 32 87 105 110 54 52 59 32 120 54 52 10.0;.Win64;.x64
29 20 41 70 70 6C 65 57 65 62 4B 69 74 2F 35 33 41 32 65 112 112 108 101 87 101 98 75 105 116 47 53 51 ).AppleWebKit/53
37 2E 33 36 20 28 4B 48 54 4D 4C 2C 20 6C 69 6B 55 46 51 54 32 40 75 72 84 77 76 44 32 108 105 107 7.36.(KHTML,.lik
65 20 47 65 63 6B 6F 29 20 43 68 72 6F 6D 65 2F 101 32 71 101 99 107 111 41 32 67 104 114 111 109 101 47 e.Gecko).Chrome/
31 32 32 2E 30 2E 30 2E 30 20 53 61 66 61 72 69 49 50 50 46 48 46 48 46 48 32 83 97 102 97 114 105 122.0.0.0.Safari
2F 35 33 37 2E 33 36 00 84 32 30 30 33 30 31 30 47 53 51 55 46 51 54 0 132 50 48 48 51 48 49 48 /537.36..2003010
37 00 47 65 63 6B 6F 00 37 1F 88 28 03 4D 6F 7A 55 0 71 101 99 107 111 0 55 31 136 40 3 77 111 122 7.Gecko.7..(.Moz
69 6C 6C 61 00 00 80 00 00 00 00 00 00 F8 3F 18 105 108 108 97 0 0 128 0 0 0 0 0 0 248 63 24 illa..........?.
39 23 18 93 31 2F 4E 65 74 73 63 61 70 65 00 3C 57 35 24 147 49 47 78 101 116 115 99 97 112 101 0 60 9#..1/Netscape.<
0B 47 6F 6F 67 6C 65 20 49 6E 63 2E 00 20 2D 20 11 71 111 111 103 108 101 32 73 110 99 46 0 32 45 32 .Google.Inc...-.
50 38 2B 20 50 4D 6F 7A 69 6C 6C 61 2F 35 2E 30 80 56 43 32 80 77 111 122 105 108 108 97 47 53 46 48 P8+.PMozilla/5.0
20 28 57 69 6E 64 6F 77 73 20 4E 54 20 31 30 2E 32 40 87 105 110 100 111 119 115 32 78 84 32 49 48 46 .(Windows.NT.10.
30 3B 20 57 69 6E 36 34 3B 20 78 36 34 29 20 41 48 59 32 87 105 110 54 52 59 32 120 54 52 41 32 65 0;.Win64;.x64).A
70 70 6C 65 57 65 62 4B 69 74 2F 35 33 37 2E 33 112 112 108 101 87 101 98 75 105 116 47 53 51 55 46 51 ppleWebKit/537.3
36 20 28 4B 48 54 4D 4C 2C 20 6C 69 6B 65 20 47 54 32 40 75 72 84 77 76 44 32 108 105 107 101 32 71 6.(KHTML,.like.G
65 63 6B 6F 29 20 43 68 72 6F 6D 65 2F 31 32 32 101 99 107 111 41 32 67 104 114 111 109 101 47 49 50 50 ecko).Chrome/122
2E 30 2E 30 2E 30 20 53 61 66 61 72 69 2F 35 33 46 48 46 48 46 48 32 83 97 102 97 114 105 47 53 51 .0.0.0.Safari/53
37 2E 33 36 00 57 69 6E 33 32 00 22 2F A0 01 07 55 46 51 54 0 87 105 110 51 50 0 34 47 160 1 7 7.36.Win32."/...
14 00 A5 01 20 0 165 1 ....

这个成员在收集的65个成员里占到第29位,当然这个到底占数组哪个位置不是固定的!
这也是Shape最难处理的地方,不同时段获取到的Shape.js对应的这个占位是不同的!
跟code流直接相关。意味着必须把code跑一遍才能拿到这个位置。

收集过程可以打日志分析函数调用取到
(call) -> 45170 Arguments(3) ['5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.…KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', Array(4), 3, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['20030107', Array(109), 108, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['Gecko', Array(118), 117, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 1028, Array(124), 123, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, -21334, Array(127), 126, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['Mozilla', Array(130), 129, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['', Array(138), 137, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 1.5, Array(139), 138, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 24, Array(148), 147, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 27, Array(149), 148, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 24, Array(150), 149, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 159, Array(152), 151, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['Netscape', Array(154), 153, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, -21334, Array(163), 162, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['Google Inc.', Array(166), 165, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 1440, Array(178), 177, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 2560, Array(180), 179, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 1400, Array(182), 181, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 2560, Array(184), 183, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWeb…KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', Array(186), 185, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 45170 Arguments(3) ['Win32', Array(298), 297, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 1515, Array(304), 303, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 20, Array(309), 308, callee: (...), Symbol(Symbol.iterator): ƒ]
(call) -> 68549 Arguments(4) [ƒ, 0, Array(310), 309, callee: (...), Symbol(Symbol.iterator): ƒ]

上面每一次函数call。都会把字符串或者其他类型数字序列化为字节集,这个时候就直接进行异或加密了。最终取到一个加密后的字节集

[35, 73, 182, 48, 124, 183, 46, 145, 161, 62, 250, 241, 195, 207, 25, 135, 17, 139, 212, 207, 240, 106, 59, 222, 246, 54, 55, 207,
3, 188, 64, 9, 71, 111, 178, 37, 183, 163, 194, 142, 193, 236, 143, 68, 199, 4, 150, 174, 243, 184, 245, 139, 123, 139, 242, 254,
161, 52, 53, 91, 198, 227, 165, 104, 86, 14, 163, 81, 77, 91, 255, 254, 69, 174, 12, 63, 45, 21, 148, 34, 121, 161, 38, 11, 226,
216, 52, 222, 4, 98, 22, 20, 52, 185, 150, 169, 205, 214, 3, 105, 95, 33, 238, 41, 35, 86, 32, 98, 129, 103, 254, 216, 228, 214,
159, 8, 80, 95, 218, 39, 74, 59, 233, 41, 34, 62, 234, 131, 152, 161, 113, 235, 197, 58, 105, 142, 184, 112, 195, 125, 99, 6, 229,
227, 248, 134, 54, 147, 17, 38, 159, 222, 44, 249, 78, 108, 233, 2, 155, 172, 26, 65, 192, 154, 78, 150, 144, 31, 205, 140, 229,
157, 23, 183, 32, 237, 214, 122, 127, 223, 53, 169, 175, 171, 181, 198, 25, 149, 150, 233, 30, 41, 224, 120, 114, 193, 216, 164,
184, 47, 196, 45, 207, 76, 85, 45, 165, 229, 224, 204, 233, 250, 70, 99, 191, 171, 225, 120, 162, 111, 231, 254, 193, 174, 1, 241,
37, 38, 16, 55, 89, 238, 28, 47, 58, 183, 169, 162, 45, 130, 127, 163, 95, 26, 66, 198, 137, 183, 102, 173, 55, 77, 203, 101, 100,
164, 131, 53, 161, 146, 137, 83, 153, 250, 173, 189, 242, 79, 146, 78, 212, 85, 144, 228, 238, 169, 93, 19, 154, 107, 54, 49, 51,
214, 120, 233, 129, 254, 31, 118, 15, 157, 149, 113, 4, 157, 129, 125, 168, 81, 23, 239, 253, 37, 101, 89, 119, 197, 31, 31, 61,
48]

因为是异或加密的结果,而且异或加密使用了随机数作为起始算子,所以这个字节集的结果不是固定的。
这里的关键就是Shape直接边序列化变进行了异或加密。分析这里真的废键盘!

异或加密

熟悉加解密的应该都见过异或加密,对于那种动态生成的数据流,异或加密是比较合适的加密方案,搞一个异或加密器,每生成一个字节就从加密器取一个异或的字节,一边序列化一边加密。Shape即是使用此法。具体流程我相信大家F8大法应该能调试清楚。
上图即可实现一个异或加密器,初始参数s3是js自带字符串校验所得。
初始参数s4就比较变态了,是随机数 * js浮点数表里的位置不固定的值。正是由于这个浮点数的位置不定,所以也决定了必须跑一遍Code流才行。

进一步加密

你以为一个简单的异或加密就完了么?不不不!异或加密只是对收集的65个成员单个成员进行了第一轮的加密。这65个成员还要组合在一起,然后进一步进行加密,这个加密才是最难分析清楚的部分。也是最废键盘的部分。
同样!插装打日志大法配合F8 F10 F11单步调试,当你差不多按坏了一个键盘,再投入至少个把月的时间精力,大概能搞清楚执行了什么。这里直接说答案:一个非标准的加密算法,一个16个成员的整数型数组,通过复杂的运算得到一个新的16个成员的整数型数组,和前面的结果进行分组异或。每组完毕后,再次进行复杂的运算,得到一个不同的新的16个成员的整数型数组,继续下一组的异或。如此循环,得到最终的加密后的字节集。
这个加密算法3年前就是如此,现在也完全没变。

变异Base64加密

通过上一步加密得到的字节集大概就是几千个字节的加密字节,这个数组当然不能直接作为请求参数,所以又进行了一层变异Base64加密。
//t就是编码表 Shape是js里自带的
function VariantBase64(str,t) {
var len = str.length;
var r = new Uint8Array(Math.floor(len * 3 / 4));
var m1, m2, m3, m4, k1, k2, k3;
for (var i = 0, j = 0; i < len; i += 4, j += 3) {
m1 = t.indexOf(str[i]);
m2 = t.indexOf(str[i + 1]);
m3 = t.indexOf(str[i + 2]);
m4 = t.indexOf(str[i + 3]);
k1 = m1 << 2 | m2 >> 4;
k2 = (m2 & 15) << 4 | m3 >> 2;
k3 = (m3 & 3) << 6 | m4;
r[j] = k1;
if (i + 2 < len) {
r[j + 1] = k2
}
if (i + 3 < len) {
r[j + 2] = k3
}
}
return r
}
至此请求头里的参数A就出来了。

3.请求头其他几个参数

a: 收集的浏览器信息加密的

b: 参数f+参数a的校验

c: code里字符串解密跑出来的
"LqUN19-tNV_7vDBqS3BjTwImSWByl9bgefykao7k2F7_PcfDTk8bWGEkCs63HonbNBwcao9mMxGoqGUH0X7iyZx6jgn6umUPuysJU0Df-Es8mzMYSJKzjd6xV_k,306"
:"AEBwWCuOAQAAIDGyOzYDrlXg7UWJqv7slOCdALh1laeWUHYLsA_5IphsAz9I"

d: code里字符串解密跑出来的
qZEp4vGadEbZjgpzMEtMYCQXWVdjxZiFT824Upytvz2FWqfKHDQ6Z3hMCqqXGt7XJERkUb1mfk-cuxU-51TkxqMTriqPv0wB0wgoNzT0w2J7unIpdtHp7-mnJtkHZiK605vjebwd5QDZU8Ydx4R11TCiiiheFmpwjB8BEu_31G9QCozrAJOV1iVMrgPVOSkeHOoQ4zpcOFTkR8Q,118
: "ABaAhIDBCKGFgQGAAYIQgISigaIAwBGAzvpizi_33wcP-SKYbAM_SP____-_HhmNAL5xVFHc5SurCp9zHqMVMDk"

f: js里带的

字符串解密过程无需分析,因为无法脱离code流。也是异或,异或用的字符串在加密字符串表中的位置也是code流里写死的。


补环境

通过上面的分析,可以看到有很多处必须把code流跑一遍的地方。这就意味着脱离这个随时间变化的Shape.js。实际是无法直接伪造参数的。加密用的参数很多都是直接编译到了code流里面。不跑一遍拿不到!那么要拿到这些参数,就只能想办法跑一遍了。无头浏览器是一个办法,但是执行效率堪忧。补环境是一个更好的办法。B站看点教程,然后慢慢补吧。直到随便拿来一个Shape.js都可以在v8里跑出一个可用的请求头参数即可。
我参考的B站教程:https://www.bilibili.com/video/BV11o4y1D7m6?p=37&vd_source=e8f93da27c5f30218e12cae3fb0ccd1b
补环境也是掉头发的活儿!尤其Shape收集的东西太多了!


展示

总结

1.Shape加密的分析真的是一个极其掉头发的活儿。根源就是他的原始明文js就很复杂,收集的浏览器信息又非常多。那编译成code流在VM里跑,光执行自定义指令就有几百万次。
2.这个东西只是繁杂,只要投入精力还是能搞清楚的。

看雪ID:rushmaster

https://bbs.kanxue.com/user-home-636891.htm

*本文为看雪论坛优秀文章,由 rushmaster 原创,转载请注明来自看雪社区

# 往期推荐

1、Windows主机入侵检测与防御内核技术深入解析

2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

3、银狐样本分析

4、使用pysqlcipher3操作Windows微信数据库

5、XYCTF两道Unity IL2CPP题的出题思路与题解

球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458557938&idx=1&sn=831352b45cf9a688c4129bfe3c58981b&chksm=b18dad7886fa246e4c7ca7b2585f701b301727993ac4ddadc93892567db9f9fc7d5a8ffa794d&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh