嘘~ 正在从服务器偷取页面 . . .

JS逆向案例


马蜂窝 2021 年 9 月 4 日


  • 第一次会返回一段cookie
  • 带上这个Cookie 请求返回一段混淆的代码
    • 混淆的代码是一个自执行 在执行的时候就会设置Cookie 再次带上就可以返回真实的页面了

第一次cookie > 第二次cookie

马蜂窝测试

马蜂窝结果


极简壁纸 2021 年 9 月 11 日


  • 涉及:JS 混淆 , AES 加密 无限 DEBUG 以及 滑块验证码识别

滑块距离加密

  • 滑块识别完成之后会返回一段 token 这段 token 加上之前请求体携带的 secretKey 加上滑块的距离 再进行一次 AES 加密,可以得到一个 headers 请求头加密数据captcha
  • 带着这个加密数据去访问网页会得到一段被加密数据

请求下载链接

  • POST 请求下载 ID 后就可以请求真实下载地址了,这里需要将 headers 补齐才会返回数据。

最终结果图


去*儿登录(2022 年 3 月 29 日)

  1. 这个网站登录有一个滑块,这个滑块是官网自己写的,没什么难度。
  2. 用这个滑块生成的一个密文,得到一个 token,拿着这个 token 去登录就可以通过了。
  3. 一个小技巧 故意输错账号密码 可以一直滑动

第一次根值

  • 滑动之后,右侧已经生成值,根据调用栈找到目标位置。 > 第二次根值
  • 往上找一层,发现 o 就是加密值,跟进再次查看。

最终根值

  • 可以看到就是 AES 加密,d 就是一个 json,
  • track 其实就是滑动轨迹,通过一个鼠标事件,不断往里面加数据。 > 二次滑动
  • 上面这张是重新滑动的,可以看到轨迹变少了。因为滑动只有一个方向,所以是可以写死的。
  • 将上面的所有分析,变为代码,结果如下。。。。 > 登录结果
  • 第二个重定向主页,响应头已经设置 Cookie,登录成功。。。。

M 眼电影(2023 年 2 月 1 日)

  • 首先这个网站的with参数 在urlheaders中都有 那么可以hook setRequestHeader
    hookAjax

  • 从上图可以看到 XMLHttpRequest 方法都被hook了 进入到open send 方法内部下断 单步走 就会跟到
    X-FOR-WITH

  • 其中w是指纹 这里不构造指纹 我们把JS放到本地 通过框架去跑 不修改JS内部代码
    指纹

  • 直接放在环境里面跑 我们知道上面那段JShook 那我们创建一个XMLHttpRequest让它去改 我们再主动调用
    error

  • 读取attributes 后直接报错了 查到是NamedNodeMap 一个节点集合 把他补上 (返回一个可迭代对象)
    error

  • 第二次报错 在官网是一个方法 没补全而已
    error

  • document.getElementById(metaId).getAttribute("content") getElementById 这个方法没补 因为需要网页的特殊元素参数 比如这里就需要一个content
    补全 getElementById
    成功

  • 这里可以在setRequestHeader 内部 把 js 代码内部设置的值导出 就可以获取到密文

🐧 音乐 V(2023 年 2 月 14 日)

  • VMP 是原子级操作 定义变量 赋值 都是需要操作的指令
  • 算法还原前置
  • 总栈 先进后出 模拟方法的调用
    • 大量的 *.push() *.pop()
  • OP 指向下一行指令 或 字节
    • t++ O+=4
  • 方法参数栈 局部作用域栈
    • let v = argument; v.push()
  • 代码段
    • 哪个参数大量使用了OP 进行读取 谁就是代码 b[t++] b[O++]
  • 方法的调用
    • 因为fromCharCode解回来的代码都是str 并且内部函数的调用不知道是多少个参数 所以还会使用call apply eval
    • 需要注意需要有上面的特征的才是

VMP分析

  • d[n[t++]]() 根据上面的规则 可以分析出 d 是总栈 n是代码字节集 tOP i在右侧可以看出是局部方法参数栈
  • 这里需要打印出局部参数栈 看下调用流程 分析出原始算法
  • i[i.length - 1] += String.fromCharCode(n[t++]) 在这一行可以发现特征fromCharCode 并且前面对 总栈i进行操作
  • 所以可以在此进行插桩console.log(JSON.stringify(i,(k,v)=>{if (v===window) {return "window"}return v;}) + '\r\n')
for (var h = !1; !h; ) {
  h = d[n[t++]]();
  window.logcat +=
    JSON.stringify(i, (k, v) => {
      if (v === window) {
        return "window";
      }
      return v;
    }) + "\r\n";
}
// 注意要加在那个调用的for循环里面

import hashlib
import re

hash_value = '"{"comm":{"cv":4747474,"ct":24,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"yqq.json","needNewCode":1,"uin":0,"g_tk_new_20200303":5381,"g_tk":5381},"req_1":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"4809023160","songmid":["002zqRMh2oSuv5"],"songtype":[0],"uin":"0","loginflag":1,"platform":"20"}},"req_2":{"module":"music.musicasset.SongFavRead","method":"IsSongFanByMid","param":{"v_songMid":["002zqRMh2oSuv5"]}},"req_3":{"method":"GetCommentCount","module":"music.globalComment.GlobalCommentRead","param":{"request_list":[{"biz_type":1,"biz_id":"394327271","biz_sub_type":0}]}},"req_4":{"module":"music.musichallAlbum.AlbumInfoServer","method":"GetAlbumDetail","param":{"albumMid":"001a1BQI2V2PtA"}},"req_5":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"7013258812","songmid":["002zqRMh2oSuv5"],"songtype":[0],"uin":"0","loginflag":1,"platform":"20"}}}"'

_hash = hashlib.md5()
_hash.update(hash_value.encode('utf-8'))
hash_code = _hash.hexdigest().upper()

print(hash_code)

result1 = ''.join([hash_code[v] for v in [22, 5, 10, 27, 17, 21, 28, 31]])
result2 = ''.join([hash_code[v] for v in [19, 12, 4, 3, 2, 8, 7, 26]])

arr1 = [212, 45, 80, 68, 195, 163, 163, 203, 157, 220, 254, 91, 204, 79, 104, 6]
arr2 = [25, 66, 1, 58, 90, 78, 123, 71, 33, 12, 178, 93, 40, 166, 62, 233]
_map = {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15}

arr3 = []
for v in range(0, len(hash_code), 2):
    n1 = _map[hash_code[v]] * 16
    n2 = _map[hash_code[v + 1]]
    n3 = n1 ^ n2
    n4 = n3 ^ arr2[v // 2]
    arr3.append(n4)

arr4 = []
for v in range(0, len(arr3) - 3, 3):
    n1 = arr3[v] >> 2  # 索引值

    n2 = arr3[v] & 3
    n3 = n2 << 4
    n4 = n3 + (arr3[v + 1] >> 4)  # 索引值

    n5 = arr3[v + 1] & 15
    n6 = n5 << 2

    _a1 = arr3[v + 2] >> 6
    n7 = n6 ^ _a1  # 索引值

    n8 = arr3[v + 2] & 63  # 索引值

    arr4.append(n1)
    arr4.append(n4)
    arr4.append(n7)
    arr4.append(n8)

arr4.append(arr3[15] >> 2)
arr4.append((arr3[15] & 3) << 4)

result4 = ''.join(["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[v] for v in arr4])

result4 = re.sub(r'[\\/+]', '', result4)

print(f'zzb{result1}{result4}{result2}'.lower()) # 'zzbbacf0a7cwtkr90cnovabaujugylolqdda071d8'
print('zzbbacf0a7cwtkr90cnovabaujugylolqdda071d8')
  • 检测点
  1. window
  2. navigator
  3. location
  4. Headless 无头浏览器
  5. reg test navigator.userAgent
  6. location.host.indexOf(“qq.com”)
  • 总结

  • 在计算机中 减法的操作性能低 正常情况下不会使用

  • 可选运算符 + - * / << >> ^ | & % 就是猜

  • 上面的代码中 一些固定的参数可能也是变动的 还需要再去分析怎么生成

瑞丽点选(2023 年 4 月 25 日)

  1. 当网页请求之后 返回的html内 可以定位到 i : 149e494640******5e3a9bcf10617

    • 这个信息可以确定是网页请求的图片背景 然后通过 background-position 来还原被混淆的图片
    • 这里每次的偏移都是不一样的
      混淆的验证码
  2. 也就是从图片上截取指定位置的图片 渲染页面上
    还原 + 其中-80 代表第二行 + 每个图片宽2080

  3. 还原核心

# 创建空白图片 透明背景
new_image_back = Image.new('RGBA', (300, 200), (255, 255, 255))
new_image_back_x = 0
new_image_back_y = 0
for div in divs:
    _x, _y = div.split('n: ')[1].replace(';', '').replace('px', '').split(' ')
    x = int(_x) * -1
    y = int(_y) * -1
    # 从 back_image 中截取图片 粘贴到 new_image_back 中
    new_image_back.paste(back_image.crop((x, y, x + 20, y + 80)), (new_image_back_x, new_image_back_y))
    new_image_back_x += 20

    if new_image_back_x == 300:
        new_image_back_x = 0
        new_image_back_y += 80
  • 然后把提示信息也增加到底部 进行识别
    还原成功

识别

  1. 这里识别的时候要注意 发过去的图片大小需要调整为和网页端一致 要不然坐标偏移结果就是错的
  2. 可以通过返回的坐标画点 看下是否准确
    识别结果
    结果

文章作者: 林木木
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 林木木 !
评论
  目录