07--JS07--逆向03:字体反爬、JS反调试破解

JS逆向03:字体反爬、JS反调试破解

jsvmp : 代码虚拟化保护方案
    
ast   : 抽象语法树

1.字体文件、字体反爬

### 1 字体文件  .ttf  .woff   或 .eot
在计算机内存中,文字就是一堆二进制(unicode),要以文字图形的样子,展示给用户看
就要指定 它们之间的对应关系,就是字体文件


书法字体、火星文字体,不同的字体文件,对同样的文字unicode,显示出来的文字图形不一样
文字unicode  <== 字体文件 ==> 显示的文字图形(按照字体文件的规则,对应显示)

eg: 帅(101011二进制)  <--- 微软雅黑字体 --->  帅(汉字的图形)

# 文字图形样子的核心:取决于 文字的unicode 和 字体文件

# windows中的font-creator软件,可以打开查看字体文件  (只有windows) 



### 2 字体反爬原理
unicode的字节范围比较大(包含世界所有文字)
网站自定义了一套字体规则,把文字unicode中 用不上的unicode码,重新进行编排,对应上文字图形
而且有可能 编排对应后的文字图形,也做过一些微处理
例如:人字,细节处多一个小点,或者 一部分很长之类的

并在网页中显示时,不是直接显示 正常的现成文字图形
而是混杂着,重新编排的一些 特定unicode码 和 正常文字图形

所以正常爬取时,爬到这些特定的文字unicode码,并按照自己的现有字体文件 加载的话,就是乱的



### 3 解决字体反爬的实现步骤: 
1.从网站中,获取字体文件

2.从字体文件中,获取所有的文字unicode码

3.先将文字unicode码,照着字体文件里面的样子,把所有对应的文字图形,画到同一张图片上去

4.再用人工智能-图片识别文字【AI_OCR】,去识别这个图片   # 识别没有百分之百的准确率
  就知道了网页中 所有该文字unicode码,对应的正确文字是什么
  # 3、4步是 对字体文件进行解析   # unicode ---> 正确的文字 (难)


5.在获取到页面源代码后
  把unicode码的文字,进行正确文字的替换   # 后续就可以正常处理了 

    
    
    
### 4 第三方图片识别文字  ---> 也可以用于识别 验证码
1.ddddocr        # python的第三方库  免费
2.Tesseract-OCR  # python的第三方库  免费

3.火山(字节)     # 第三方 收费
4.百度AI(比上面这几个都好一些)

2.案例

2.1 获取字体文件

### 1 从网站中,获取到字体文件
import requests
from lxml import etree
from urllib.parse import urljoin
import re

# 1.先加载 index.html
url = "http://bikongge.com/chapter_1/font_1/index.html"
resp = requests.get(url)
resp.encoding = 'utf-8'
# print(resp.text)


# 2.从index.html中,获取style.css的url
tree = etree.HTML(resp.text)
href = tree.xpath("//link/@href")[0]
href = urljoin(url, href)
# print(href)


# 3.从style.css中,获取字体文件的url
font_resp = requests.get(href)
font_url_re = re.compile(r'src: url\("(?P<font_url>.*?)"\);', re.S)
font_url = font_url_re.search(font_resp.text).group("font_url")
# print(font_url)

# url拼接    这个url应该和谁拼接,是相对应style.css的,和style.css的url拼接
font_url = urljoin(href, font_url)
print(font_url)


# 4.下载字体文件
font_resp = requests.get(font_url)
with open("font.woff", mode="wb") as f:
    f.write(font_resp.content)


# 对比查看:
s = "\ue0df"    # 文字unicode码
print(s)        # 直接打印(默认unicode编码打印):一个奇怪的乱字

# 但实际是    windows软件font-creator打开看
'\ue0df' => 店 (字体文件)

2.2 获取文字unicode码

# 2 从字体文件中,获取所有的文字unicode码  需要font-tools库
# pip install fontTools

# TTFont('字体文件')的方法
  .getGlyphOrder()  # 获取所有文字的unicode码   有顺序  推荐使用
  .getGlyphNames()  # 获取所有文字的unicode码   无顺序


from fontTools.ttLib import ttFont

ttf = ttFont.TTFont("font.woff")
uni_list = ttf.getGlyphOrder()[2:]   # 推荐使用, 有顺序  ['unie467', 'uniee7e'...]

# 将uni字符串,转化为 python的unicode格式   \u:unicode码
uni_ok_list = []
for uni in uni_list:
    uni = uni.replace("uni", "\\u")  # 不能直接 \u,会被直接识别为unicode,再加个\ 防止转义
    uni_ok_list.append(uni)
    
    
print(uni_ok_list)
print(len(uni_ok_list))  # 601

2.3 根据字体文件画图

# 3 按照字体文件,把文字unicode码的文字图形,画出来
# 安装 pip install pillow
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw


# 1.创建画布    参数:模式-RGB彩色、大小、颜色
img = Image.new(mode="RGB", size=(2000, 1000), color=(255, 255, 255))

# 2.准备一只可以在图上画画的笔
img_draw = ImageDraw.Draw(img)

# 3.准备好画图的字体    画的字体、字体每个字的像素大小
img_font = ImageFont.truetype("font.woff", size=50)

# 4.准备开始画图,需要画图规划    不能全在一行,需要换行
# 例如:一行40个字 (50*40=2000,画布宽2000,正好够)

# 换行逻辑:
# 根据所有文字unicode码列表的索引号, 若 索引 % 40 == 0,(取余) 就该换行了

# 行号:  索引 // 40   # 整除,向下取整   从第0行开始
# 目的是为了计算,每行画笔的高度  y ---> (索引 // 40) * 每行的高度


# 指定每行字符的个数
line_length = 40

# 指定每行的高度  像素
line_height = 60

# 把一行需要画的字符,放进一个列表
char_line = []

for i in range(len(uni_ok_list)):
    uni = uni_ok_list[i]
    # 把 '\\uxxxx' 修改成unicode码,需要 用编码的转换 来进行调整  先编码成字节,再按照unicode解码
    uni = uni.encode().decode("unicode-escape")

    if i % line_length == 0 and i != 0:  # 遍历到需要换行的字符了
        # 1.先把上一行的字符,同时画到画布上
        char_line_s = "".join(char_line)

        # 画笔写字(.text)的参数:画的坐标(x,y)、文本内容、fill(填充颜色 数字或单词)、参照的字体
        img_draw.text((0, (i // line_length - 1) * line_height), text=char_line_s, fill=1, font=img_font)

        # 2.本次遍历到字符 需要换行,直接覆盖上一次的行列表
        char_line = [uni]
    else:
        # 正常需画在同一行的字符,追加行列表
        char_line.append(uni)


# 再画,最后不足一整行的字符
if char_line:
    char_line_s = "".join(char_line)
    img_draw.text((0, (len(uni_ok_list) // line_length) * line_height), text=char_line_s, fill='red', font=img_font)

    
# 完成上述操作,你只是在内存中画了一张图

# 保存到硬盘上
img.save("tu.jpg")

2.4 图片识别文字

# 4 接下来就是识别上面这张图,获取到所有文字   ---> 百度AI
pip install baidu-aip
# 该库 还需要其他库
pip install chardet


from aip import AipOcr
 
"""刚才创建的那个应用里. 有下面这三个东西"""
APP_ID = '27643903'
API_KEY = 'atSZosOQ1xOQmBOmGSgLnbNA'
SECRET_KEY = 'znj959V9ZN0zlGVhZexABFT4ek57Kom2'

client = AipOcr(APP_ID, API_KEY, SECRET_KEY)

f = open("tu.jpg", mode="rb")
r = client.basicGeneral(f.read())


# 所有文字
result_list = []
for item in r['words_result']:
    result_list.extend(item['words'])
    
    
print(len(result_list))  # 注意:调整识别后的文字数量 是否与 unicode码字数量 一致

2.5 解析替换

### 5 替换--反向
# 5.1 把unicde对应的word,进行映射 ---> 字典
  # zip() 有水桶效应(以短的一方,为边界) 
  # 故:uni_ok_list和result_list必须长度保持一致.

dic = dict(zip(uni_ok_list, result_list))
# 结果:{"\\uxxxx": 天}



# 5.2 获取页面源代码,并把字典中'\\u' ,按照页面的形式,换成 '&#X'
# 5.3 再页面源码的'&#X',替换成 识别后的文字
url = "http://bikongge.com/chapter_1/font_1/index.html"
resp = requests.get(url)
resp.encoding = 'utf-8'

page_source = resp.text

for k in dic:
    # k: \\uxxxxx
    # v: 天
    v = dic[k]

    kk = k.replace("\\u", "&#x") + ";"
    # 字符串不可变. 需要重新赋值
    page_source = page_source.replace(kk, v)

print(page_source)

3.JS反调试破解

  • 常见的反调试技术

    • 禁用调试快捷键:禁用鼠标右键、禁用快捷键F12,无法打开调试控制台

    • 检测调试器工具:通过检测调试器工具的存在,判断是否处于调试环境

    • 检测浏览状态:通过检测浏览器状态的变化(例如:浏览器窗口的大小),判断是否处于调试环境

    • 定时检测:定时检测调试器状态,若检测到调试器存在,则执行相应的反调试代码(debugger)

    • 模糊代码:对关键代码进行混淆或加密,使得难以分析和理解

  • 破解本质:

    • 1.绕过调试检测
    • 2.不让它执行反调试函数 (基本都是将相应的反调试函数置空)

    在网站执行它这些反调试函数之前,先执行我们自定义的JS代码,改变它的反调试函数代码,让其反调试不起作用。

  • 破解手段:

    • 1.根据其反调试代码函数,进行相应函数的Hook,详见JS02-高级:3.11 JS Hook

    • 2.通过油猴插件,写JS脚本进行破解 两者本质一致

3.1 简单无限反调试

  • 通过debugger断点定时递归,实现无限反调试
function Debugging(){
    debugger;
    setInterval(Debugging, 1000);
};
Debugging();
  • 定时清空控制台,干扰console.log调试
function startClearConsole(){
    // 每个一秒钟,清除控制台内容
    setInterval(function(){
        consolo.clear();
    }, 1000);
};
startClearConsole();

3.2 反调试综合案例

  • 案例网站:https://www.aqistudy.cn/
  • 禁用F12、禁用鼠标右键、检测开启调试器、无限debugger、无限清除控制台、代码运行时间差检测、窗口尺寸差检测

  • 写油猴破解脚本
// 前提:安装油猴插件、编写脚本、开启脚本

// ==UserScript==
// @name         反调试脚本
// @namespace    https://www.aqistudy.cn/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.aqistudy.cn/
// @match        http://www.aqistudy.cn/
// @match        https://www.aqistudy.cn/*
// @match        *://*/*
// @icon         https://www.google.com/s2/favicons?domain=aqistudy.cn
// @run-at       document-start
// @grant        unsafeWindow
// ==/UserScript==


// 注意:@run-at  脚本运行时刻  
document-end     脚本在DOMContentLoaded事件发生时或之后被注入   ***
document-idle    脚本在DOMContentLoaded事件发生后被注入. 默认值
document-start   脚本在最早的时刻被注入
document-body    当body元素存在时脚本被注入

// 注意:@grant   油猴权限获取  
none          脚本直接运行在前端页面中,但无法与油猴脚本交互,无法使用油猴更强的一些功能
              脚本的window,就前端的window
unsafeWindow  脚本默认运行在一个沙盒环境中,使用unsafeWindow代替前端的window,与前端交互
              且能使用油猴其他功能     ***  
GM_* 函数      油猴一些特定的功能,需要单独授权



(function() {
    'use strict';
    // 设置全局变量debugflag为false,表示未检测到调试工具
    unsafeWindow.debugflag = false;
    // 取消检测调试工具的函数,将其设置为空函数
    unsafeWindow.endebug = function(){};
    // 关闭无限debugger函数的调用,将其设置为空函数
    unsafeWindow.txsdefwsw = function(){};
    // 取消禁止鼠标右键,将document.oncontextmenu设置为空函数
    document.oncontextmenu = function(){};
    // 取消禁止F12,将document.onkeydown设置为空函数
    document.onkeydown = function(){};
    // 取消代码运行时间差的debugger
    var fnc_ = unsafeWindow.Function.prototype.constructor;
    unsafeWindow.Function.prototype.constructor = function(){
        if(arguments[0]==='debugger'){
            return function(){};  // 返回空函数对象
        } else {
            return fnc_.apply(this, arguments);
        }
    }
    // 禁用定时器
    unsafeWindow.setInterval = function(){};
    console.log(3333333333333333)
})();



// 目前:
先打开F12,这些脚本代码能有效果 不断点反调试了
但若是直接右键和F12,还是不能打开开发者工具
posted @ 2024-07-12 15:30  Edmond辉仔  阅读(47)  评论(0编辑  收藏  举报