JS基础

变量声明

在JS中,可以通过var关键字来声明变量

1
2
3
var name;

name = "S1nJa"

image-20240604144507425

也可以一次定义多个变量

1
var name = "S1nJa",age=18

函数

1
2
3
4
5
function add(a,b){
c = a+b
return c
}
add(1,1)

image-20240604163744516

常用函数

length

返回字符串的长度。

image-20240604145252903

indexOf

用于查找指定字符串在另一个字符串中第一次出现的位置。

image-20240604145555752

slice

slice(start, end)

从一个字符串中返回指定起始索引和结束索引之间的子串

image-20240604150131539

substring

substring(start, end)

substring方法类似于slice,不同的是substring方法无法接受负数索引。

image-20240604150259698

substr

substr(start, length)

substr的第二个形参接受的不是结束为止,而是截取长度

image-20240604150455836

replace

用于字符串替换

image-20240604150621528

concat

用于字符串拼接

image-20240604151259264

push

向数组中添加元素

image-20240604151929792

join

将所有元素合并为一个字符串

image-20240604152329910

toUpperCase() 用于将字符串转为大写

toLowerCase() 用于将字符串转为小写

trim() 删除字符串两端的空白符,同Python中的strip()一样的功能。

charAt() 方法返回字符串中指定下标(位置)的字符串

split() 分割字符串

pop() 从数组中删除最后一个元素

push() 向数组结尾处添加新的元素

shift() 删除数组最后一个元素

unshift() 向数组开头处添加元素

sort() 对数组进行排序(按照数字大小或者字母顺序)

reverse() 反转数组中的元素。

条件运算

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
26
27
28
29
if (condition){
语句
}
...................
if (condition){
语句
}else{
语句
}
...................
if (condition1){
语句
}else if (condition2){
语句
}else{
语句
}
...................
switch(n)
{
case 1:
代码块 1
break;
case 2:
代码块 2
break;
default:
代码块3
}

image-20240604154230394

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
for (语句 1; 语句 2; 语句 3){
语句
}

while (condition){
语句
}

do{
语句
}
while (condition);

image-20240604163302254

JS调试

在JavaScript中,断点调试是一种常用的调试技术,它允许开发者在代码的特定位置暂停执行,以便检查和分析程序的状态。

当发现可疑函数时,可以通过在断点处暂停,查看和修改变量的当前值,观察每一步数值的变化。

当打下断点后,重新请求页面便会进入调试模式

image-20240604170155096

主要分为以下功能:

①逐过程执行:常用存在多个断点,需要在多断点间进行调试使用

②逐语句执行:步过,遇到自定义函数也当成一个语句执行,而不会进入函数内部

③:与①一致

④:与②一致

⑤步入:遇到自定义函数就跟入到函数内部步出,跳出当前自定义函数

⑥步出:跳出当前自定义函数

除此外右侧部分可以查看作用域(变量的内容)和当前的调用栈等信息

加密方法定位

定位的方式有很多如提交表单时存在onClick寻找加密函数,Event Listeners定位,全局搜索等,就不一一介绍了,主要看下JS函数检测辅助工具

v_jstools

安装

下载解压后直接添加扩展即可

GitHub - cilame/v_jstools: 模仿着写一个 chrome 插件,用来快速调试前端 js 代码。

打开hook开关,根据需求进行配置即可
image-20240604172505063

使用

这里使用的是师傅编写的JS逆向练习靶场,https://github.com/0ctDay/encrypt-decrypt-vuls/

开启后重新发送请求,控制台会提示inject start!即开启成功

image-20240604172915924

1、此时发送请求,即可对我们设置的功能点进行hook,找到了明文请求的点并返回hook的位置

image-20240606160454969

2、在hook点打断点调试,寻找将数据进行加密的函数

n即为原始的明文数据,当调试到t.data = l(n);,发现经过l()函数,t.data的值发生了改变,并且对应的值就是我们传入数据加密后的值,因此l()函数一定就是要找的加密函数

image-20240606161046475

3、跟进l()函数,t为传入的明文数据,f为秘钥,h为偏移量

image-20240606162647170

4、同时在调用l()前,发现了requestID、时间戳、签名

image-20240606162943107

修改数据包

定位到加密函数后,就要着手修改数据包了

JS-RPC

https://github.com/jxhczhl/JsRpc

JS-RPC是一款远程调用浏览器方法的工具,通过websocket与本地python服务端相连。当python中想要执行代码时,只需要通过RPC即可调用控制台中的函数,不需要再本地还原。

使用

JsRpc/README.md at main · jxhczhl/JsRpc · GitHub

1、打开客户端,将JSRPC项目中的JsEnv.js中的代码复制到控制台中

2、输入var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=S1nJa");建立连接

image-20240607153318725

3、经过刚才的调试我们已经知道了l()函数即为加密函数,那便可打断点记录该函数进行调用

image-20240607155424020

4、将其注册到JSRPC

1
2
3
4
demo.regAction("enc", function (resolve, param) {
var res = enc(String(param));
resolve(res);
})

image-20240611142904866

5、注册完成后即可进行本地调用了

1
http://127.0.0.1:12080/go?group=S1nJa&action=enc&param=test

image-20240611143019756

6、解密函数同理,通过调试发现解密函数为d(),直接记录注册

image-20240611162553743

JS-RPC+MITM

mitmproxy - an interactive HTTPS proxy

mitmproxy作为一款代理工具,可以通过python对请求进行拦截,篡改,从而达到hook的目的

经过前边的分析可知,我们的每次请求都包含以下参数

image-20240611163136081

  • r:时间戳
  • n:Json格式的原始数据
  • i:requestId
  • s:签名信息
  • l:加密函数
  • d:解密函数

所以如果想要构造请求,就需要获取到以上参数,这里对明文请求进行加密,密文回显解密为例

JS-RPC

首先还是注册JS-RPC,对每个参数进行记录注册:

记录:

1
2
3
4
5
6
7
8
9
10
11
12
//时间戳
window.time = Date.parse
//requestId
window.id = p
//v函数
window.v1 = v
//签名
window.m = a.a.MD5
//加密函数
window.enc = l
//解密函数
window.dec = d

注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
demo.regAction("req", function (resolve,param) {

let timestamp = time(new Date());
let requestid = id();
let v_data = JSON.stringify(v1(param));
let sign = m(v_data + requestid + timestamp).toString();

let encstr = enc(v_data);

let res = {
"timestamp":timestamp,
"requestid":requestid,
"encstr":encstr,
"sign":sign
};
resolve(res);
})

image-20240611163956481

注册dec

1
2
3
4
5
6
demo.regAction("dec", function (resolve, param) {
var res = dec(String(param));
resolve(res);
})
#测试
http://127.0.0.1:12080/go?group=S1nJa&action=dec&param=wyJy7dTITM1EBaQzVmT%2Blw==

测试

image-20240611164254993

MITM

现在通过JS-RPC已经可以获取到,时间戳、requestid、sign、加解密内容,之后就可以通过Mitmproxy对请求响应进行hook了

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
26
27
28
29
30
31
32
33
34
35
36
import json
from mitmproxy import http
import requests

def getHeader(method,body):
data = {"group": "S1nJa", "action": method, "param": body}
res = requests.post("http://127.0.0.1:12080/go", data=data)
res_json = json.loads(res.text)["data"]
return res_json

def request(flow: http.HTTPFlow) -> None:
if flow.request.pretty_url.startswith("http://127.0.0.1:8085/api/"):
# 获取原始请求
original_body = flow.request.content.decode('utf-8')
res_json = getHeader("req",original_body)
data_json = json.loads(res_json)

# 获取生成的请求信息
encrypted_body = data_json["encstr"]
request_id = data_json["requestid"]
timestamp = data_json["timestamp"]
sign = data_json["sign"]

# 添加或替换数据
flow.request.text = encrypted_body
flow.request.headers["requestId"] = request_id
flow.request.headers["timestamp"] = str(timestamp)
flow.request.headers["sign"] = sign

def response(flow: http.HTTPFlow) -> None:
content_type = flow.response.headers.get("Content-Type", "")
if "application/json" in content_type:
# 获取响应体
response_body = flow.response.get_text()
res = getHeader("dec",response_body)
flow.response.set_text(res)

运行mitmproxy

1
mitmproxy -p 8083 -s mit.py

将8083端口设为burp的上游代理

image-20240611173126996

此时明文发包,可以看到数据会经过mitm加密处理,返回包也为明文数据

image-20240611173552205

JS-RPC+autoDecoder

GitHub - f0ng/autoDecoder: Burp插件

autoDecoder是f0ng师傅编写的一款Burp插件,同样可以与JS-RPC进行联动,达到hook的目的

插件中自带了一些常规的加密方式如aes、des、RSA、国密等

image-20240612095129011

特殊加密方式可以通过修改项目中的flasktest.py与JS-RPC进行联动

autoDecoder

JS-RPC注册跟MITM中方式一样,加密部分由于需要替换请求头时间戳、requestid、sign还未实现,暂贴解密部分代码

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
26
27
28
29
30
31
32
from flask import Flask, Response, request
import requests
import json


def getHeader(method, body):
data = {"group": "S1nJa", "action": method, "param": body}
res = requests.post("http://127.0.0.1:12080/go", data=data)
res_json = json.loads(res.text)["data"]
return res_json


app = Flask(__name__)


@app.route('/decode', methods=["POST"])
def decrypt():
param = request.form.get('dataBody') # 获取 post 参数
param_headers = request.form.get('dataHeaders') # 获取 post 参数
param_requestorresponse = request.form.get('requestorresponse') # 获取 post 参数

res = getHeader("dec", param)
print(param)
if param_requestorresponse == "request":
return param_headers + "\r\n\r\n\r\n\r\n" + param
else:
return res


if __name__ == '__main__':
app.debug = True
app.run(host="0.0.0.0", port="8888")

测试调试

image-20240612144953869

成功后,便可抓包选择加密部分右键选择autodecoder->Decode-Autodecoder进行解密

JS-PRC+YAKIT

在前边MITM方式中,使用的是mitmproxy自带库,对原始http请求进行拦截修改,而yakit中拥有热加载功能,此功能中有两个魔方方法beforeRequestafterRequest,他们分别可以在发送数据包前和对每一个请求的响应再做一次处理

而yakit的官方[poc库](poc | Yak Program Language (yaklang.com)),可以对HTTP请求进行处理,这样结合beforeRequestafterRequest便可对请求响应进行hook

JS-RPC跟前边一致

热加载加解密

热加载代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func getHeader(method,body){
res,rep,err = poc.Post("http://127.0.0.1:12080/go",poc.replaceBody("group=S1nJa&action="+method+"&param="+body, false),poc.appendHeader("content-type", "application/x-www-form-urlencoded"))
if(err){
return(err)
}
res_json = json.loads(res.GetBody())["data"]
return res_json
}


// beforeRequest 允许发送数据包前再做一次处理,定义为 func(origin []byte) []byte
beforeRequest = func(req) {
//获取请求体
original_body = poc.GetHTTPPacketBody(req)
//加密
res = getHeader("req",string(original_body))
//获取其他的参数
res = json.loads(res)

//修改其他的请求头
req = poc.ReplaceHTTPPacketHeader(req, "requestId", res["requestid"])
req = poc.ReplaceHTTPPacketHeader(req, "timestamp", res["timestamp"])
req = poc.ReplaceHTTPPacketHeader(req, "sign", res["sign"])

//修改请求体
req = poc.ReplaceHTTPPacketBody(req, res["encstr"])


return []byte(req)
}

// afterRequest 允许对每一个请求的响应做处理,定义为 func(origin []byte) []byte
afterRequest = func(rsp) {
hd,body = poc.Split(rsp)
rss = getHeader("dec",codec.EncodeUrl(string(body)))
rsp = poc.ReplaceHTTPPacketBody(rsp,rss)
return []byte(rsp)
}

// mirrorHTTPFlow 允许对每一个请求的响应做处理,定义为 func(req []byte, rsp []byte, params map[string]any) map[string]any
// 返回值回作为下一个请求的参数,或者提取的数据,如果你需要解密响应内容,在这里操作是最合适的
mirrorHTTPFlow = func(req, rsp, params) {
return params
}

通过热加载,成功将明文数据加密并发送请求进行fuzz,响应体通过解密返回明文数据

image-20240612185819439

添加插件解密

先注册解密函数

1
2
3
4
5
window.dec = d
demo.regAction("dec", function (resolve, param) {
var res = dec(String(param));
resolve(res);
})

新建插件(插件—>本地—>新建插件)

image-20240613094017966

选择Yak-codec

image-20240613094228717

源码

1
2
3
4
5
6
7
handle = func(origin /*string*/) {
group = "S1nJa";
action = "dec";
rsp,rep = poc.Post("http://127.0.0.1:12080/go",poc.replaceBody("group="+group+"&action="+action+"&param="+codec.EncodeUrl(origin), false),poc.appendHeader("content-type", "application/x-www-form-urlencoded"))~

return json.loads(rsp.GetBody())["data"];
}

保存后便可通过右键菜单中的JS解密,将密文转换为明文

image-20240613094526462

参考链接

poc | Yak Program Language (yaklang.com)

奇安信攻防社区-在渗透的”幽灵模式”中开启”透视”外挂 (butian.net)

奇安信攻防社区-死磕某小程序 (butian.net)

奇安信攻防社区-某众测前端解密学习记录 (butian.net)

保姆级教程—前端加密的对抗(附带靶场) - 先知社区 (aliyun.com)