吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4371|回复: 142
上一主题 下一主题
收起左侧

[Web逆向] 【JS逆向】某茄小说逆向分析

  [复制链接]
跳转到指定楼层
楼主
littlewhite11 发表于 2024-11-30 12:38 回帖奖励
本帖最后由 littlewhite11 于 2024-11-30 12:43 编辑

逆向目标

  • 网址:aHR0cHM6Ly9mYW5xaWVub3ZlbC5jb20vcmVhZGVyLzczOTIxNjQxOTgzNzA3ODc4NjU=
  • 目标:成功拿到文章内容

抓包分析

首先点击下一章,发起一个请求,本篇文章主要讲字体反爬,查询参数是什么就不过多说了,某大厂比较热门的一个参数。该参数逆向的话,总的来说还是一个vmp,找到函数调用的地方插桩,看日志,然后配合条件断点,耐心细心,最后一定可以弄出来的。

我们看响应,一大堆乱码,有很大的概率是字体反爬。

进一步验证一下,有字体文件。

有CSS样式,字体反爬没错了。

因为不常遇到字体反爬,所以这方面的经验相对较少。

我简单说下我的处理方式,请各位路过的大佬指导指导小弟。

首先观察字体反爬的类型,雪碧图?样式偏移?还是其他的...

其次看是不是动态字体,就是数据接口会不会连带有和字体相关的请求

最后就是字体解析,文件类型的话直接拿映射表,雪碧图和样式偏移就找对应的偏移量...

逆向分析

根据前面分析的流程,我们知道dc027189e0ba4cd大概率就是字体文件。

我们可以直接双击下载字体文件,然后找一个网站进行解析。

字体文件解析网址:aHR0cHM6Ly9rZWtlZTAwMC5naXRodWIuaW8vZm9udGVkaXRvci8=

很不给面子,说错误的ttf文件(网上说可能woff2是较新的格式,然后网站不支持解析这种类型的字体文件)。

那我们就模拟请求下载字体文件,经过了一番尝试,可以下载otf类型的文件并完成解析,我是直接把文件后缀改成了otf,可能服务器并没有校验文件的类型,如果有大佬试出了其他类型的,或者有其他方法的,也可以分享分享。

python代码:

import requests

headers = {
    "accept": "application/font-otf",
    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,pt;q=0.7",
    "cache-control": "no-cache",
    "origin": "脱敏信息",
    "pragma": "no-cache",
    "priority": "u=0",
    "referer": "脱敏信息",
    "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "font",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "cross-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
}
url = "脱敏信息/dc027189e0ba4cd.otf"
response = requests.get(url, headers=headers)

with open('test.otf', 'wb') as f:
    print(response.content)
    f.write(response.content)

然后我们回到网站,再分析分析响应的内容,

我们随便选一个文字,就“人”这个字吧,响应中是不可见字符,仔细观察网页上的,和正常的文字也会有点区别。

虽然这是不可见字符,但我们可以得到它的unicode码点,且这个码点大概率是在字体文件有对应关系的。

可以在下图这复制,然后去控制台拿到这个不可见字符的unicode码点' '.charCodeAt()得到58562

然后我们回到字体解析网站找“人”这个字,发现确实对应了gid58562,gid是标识字形的唯一标识符。

按照往常的经验,标准的字体文件每个字的唯一标识符gid会对应自己的unicode编码

我们可以看看“人”这个字的unicode编码是多少,发现是20154,对不上,那这字体文件应该就是自定义的,也就是说我们不能通过字体文件中的唯一标识符知道是什么字。

那我们该怎么处理才能拿到文字和唯一标识符gid的对应关系呢,我瞎摸索出了两种比较鸡肋的方法,都是基于svg转图片然后进行文字识别的。

第一种

半自动化方案,有点鸡肋,运行程序后需要手动加载字体文件,然后控制台回车就可以。

这种方案只适合非动态字体,只是减少工作量。

import time
import cairosvg
from DrissionPage import ChromiumPage
from lxml import etree
import os

class FontSpider:
    # 图片文件夹路径
    if not os.path.exists('./test'):
        os.mkdir('./test')

    @staticmethod
    def svg_to_image(html_code):
        tree = etree.HTML(html_code)
        svg_lst = tree.xpath('//*[@class="glyf-list"]/div/svg')
        unicode_lst = tree.xpath('//*[@class="glyf-list"]/div/div[last()]/text()')
        for svg, unicode in zip(svg_lst, unicode_lst):
            if unicode.startswith('.'):
                continue
            svg.set('width', '1000')
            svg.set('height', '1000')
            svg_str = etree.tostring(svg, encoding='unicode', pretty_print=True)
            print(svg_str)
            # 将SVG字符串转换为PNG文件
            cairosvg.svg2png(bytestring=svg_str, write_to=f'./test/{unicode.replace("gid", "")}.png', background_color='white')

    @staticmethod
    def get_single_char_image():
        driver = ChromiumPage()
        driver.get("https://kekee000.github.io/fonteditor/")
        input('加载字体文件完毕后回车>>>')
        FontSpider.svg_to_image(driver.html)
        for i in range(3):
            driver.ele('xpath://*[@id="glyf-list-pager"]/button[3]').click()
            time.sleep(1)
            FontSpider.svg_to_image(driver.html)
        driver.quit()

if __name__ == '__main__':
    FontSpider.get_single_char_image()

第二种

这种方案直接将字体文件解析为svg然后转为png图片。

from fontTools import ttLib
from fontTools.pens.svgPathPen import SVGPathPen
import pathlib
import cairosvg

class FontConverter:

    def __init__(self, font_path):
        self.font = ttLib.TTFont(font_path)
        self.units_per_em = self.font['head'].unitsPerEm

    def get_glyph_names(self):
        return self.font.getGlyphOrder()

    def get_cmap(self):
        return self.font.getBestCmap()

    def glyph_to_svg(self, glyph_name):
        # 获取字形对象
        glyph_set = self.font.getGlyphSet()
        glyph = glyph_set[glyph_name]

        # 创建SVG路径笔
        pen = SVGPathPen(glyph_set)

        # 绘制字形
        glyph.draw(pen)

        # 获取路径数据
        path_data = pen.getCommands()

        # 获取边界框
        bbox = None
        if hasattr(glyph, 'xMin'):
            bbox = {
                'xMin': glyph.xMin,
                'yMin': glyph.yMin,
                'xMax': glyph.xMax,
                'yMax': glyph.yMax
            }

        return {
            'path': path_data,
            'bbox': bbox
        }

    def create_svg(self, glyph_name, width=1000, height=1000):
        glyph_data = self.glyph_to_svg(glyph_name)

        # 计算变换参数
        scale = 0.9  # 缩放因子
        baseline = self.units_per_em * 0.8  # 基线位置

        svg = f'''<svg viewBox="0 0 {width} {height}" width="{width}" height="{height}">
            <g transform="scale(1, -1) translate(0, -{baseline}) scale({scale}, {scale})">
                <path d="{glyph_data['path']}" fill="black"/>
            </g>
        </svg>'''

        return svg

    def batch_convert(self, image_folder):
        output_path = pathlib.Path(image_folder)
        output_path.mkdir(parents=True, exist_ok=True)

        # 获取字符映射
        cmap = self.get_cmap()

        # 转换每个字形
        for unicode_value, glyph_name in cmap.items():
            try:
                # 创建SVG
                svg = self.create_svg(glyph_name)
                # 将SVG字符串转换为PNG文件
                cairosvg.svg2png(
                    bytestring=svg,
                    write_to=f'{image_folder}/{unicode_value}.png',
                    background_color='white',
                    output_height=64,
                    output_width=64
                )

            except Exception as e:
                print(f"转换字形 {glyph_name} (U+{unicode_value:04X}) 时出错: {e}")

def main(fp, image_folder):
    # 使用示例
    converter = FontConverter(fp)

    # 获取所有字形名称
    glyph_names = converter.get_glyph_names()
    print(f"字体包含 {len(glyph_names)} 个字形")

    # 批量转换
    converter.batch_convert(image_folder)

if __name__ == '__main__':
    font_path = './font.otf'
    image_folder = 'test'
    main(font_path, image_folder)

文件名都是每个字的唯一标识符gid。

然后我们需要进行文字识别来构造一个映射,键为gid,值就是哪一个字。

识别用的开源库ddddocr,我们将识别结果保存为一个json文件方便后续使用。

import os
import ddddocr
from pathlib import Path
import json
from PIL import Image
import io
import matplotlib.pyplot as plt

ocr = ddddocr.DdddOcr(show_ad=False, beta=True, use_gpu=True)
# 图片保存的文件夹路径
image_folder = 'test'
dir_path = Path(image_folder)
files = list(dir_path.glob('*.png'))

with open('mapping.json', 'w', encoding='utf-8') as mf:
    data = {}
    for file in files:
        file_name = file.name
        [unicode, _] = file_name.split('.')
        with open(os.path.join(image_folder, file_name), 'rb') as f:
            img_bytes = f.read()
        result = ocr.classification(img_bytes)
        if len(result) > 1:
            # 从字节数据创建图片
            img = Image.open(io.BytesIO(img_bytes))
            plt.imshow(img)
            plt.axis('off')  # 隐藏坐标轴
            plt.show()
            result = input('请输入正确的结果>>>')
            data[unicode] = result
        else:
            data[unicode] = result
    mf.write(json.dumps(data))

上面这个方案可能还需要手动处理一下,因为模型识别不是百分百的准确率,我们暂且认为识别出一个字的就是正确的,两个字的就需要手动处理一下。

最后我们模拟请求一下,并从json文件中取对应的映射来替换不可见字符。

import json

import requests
import re

with open('mapping.json', 'r') as f:
    mapping = json.load(f)

def get_data():
    headers = {
        "accept": "application/json, text/plain, */*",
        "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,pt;q=0.7",
        "cache-control": "no-cache",
        "ismobile": "0",
        "pragma": "no-cache",
        "priority": "u=1, i",
        "referer": "脱敏信息",
        "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\"",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
    }
    cookies = {}
    url = "脱敏信息"
    params = {}
    response = requests.get(url, headers=headers, cookies=cookies, params=params)
    print(response.text)

    content: str = response.json()['data']['chapterData']['content']
    content_list = re.findall(r'<p>(.*?)</p>', content)
    for c in content_list:
        target_content = []
        for char in c:
            if '\\u' in repr(char):
                hex_str = repr(char).replace('\\u', '')
                target_char = mapping[str(int(hex_str.strip('\''), 16))]
                target_content.append(target_char)
            else:
                target_content.append(char.strip('\''))
        print(''.join(target_content))

get_data()

成功!!!

解析字体的第二种方案,加上一个好的文字识别模型,其实能够做到不用手动干预了,奈何本菜鸡不会训练模型。

免费评分

参与人数 34威望 +2 吾爱币 +132 热心值 +26 收起 理由
yjn866y + 1 + 1 热心回复!
aigc + 1 热心回复!
weidechan + 1 用心讨论,共获提升!
wangxiaoqiqiqi + 1 + 1 我很赞同!
Hegemonism + 1 用心讨论,共获提升!
mutianj + 1 + 1 用心讨论,共获提升!
matatron + 1 用心讨论,共获提升!
1783780690 + 1 + 1 用心讨论,共获提升!
likebbs + 1 用心讨论,共获提升!
lufenfei147 + 1 我很赞同!
windrj + 1 + 1 我很赞同!
MoonlightCloud + 1 用心讨论,共获提升!
timeni + 1 + 1 用心讨论,共获提升!
Analytic + 1 我很赞同!
lichwil57120 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
yhyclown + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wqstudyy + 1 + 1 谢谢@Thanks!
鸣蜩十四 + 1 + 1 我很赞同!
sngss + 1 用心讨论,共获提升!
吉吉国王1111 + 1 + 1 用心讨论,共获提升!
xql123456 + 1 我很赞同!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
DIYS + 1 + 1 我很赞同!
Issacclark1 + 1 谢谢@Thanks!
lingyun011 + 1 + 1 热心回复!
smiler2020 + 1 + 1 用心讨论,共获提升!
hxtlcc + 1 + 1 我很赞同!
Tusimple + 1 + 1 热心回复!
wyl0205 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Xavier1 + 1 + 1 我很赞同!
melooon + 1 + 1 我很赞同!
涛之雨 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
helian147 + 1 + 1 热心回复!
Mo. + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
koukoncd 发表于 2024-12-1 16:42
照着大佬的思路,curl下otf文件,导入font文件夹,然后将div中内容去标签,换回车,贴到word文件中,然后将字体选择成刚导入的oft字体,就能看到了。然后转成PDF文档,就是电子书重制思路了



免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
yjn866y + 1 + 1 用心讨论,共获提升!

查看全部评分

沙发
FCGkitty 发表于 2024-11-30 13:05
牛逼,学习新知识了。。。现在为什么都要对请求地址加密,不至于吧,这都是用啥加密的。。。
3#
YIUA 发表于 2024-11-30 13:07
4#
 楼主| littlewhite11 发表于 2024-11-30 13:08 |楼主
FCGkitty 发表于 2024-11-30 13:05
牛逼,学习新知识了。。。现在为什么都要对请求地址加密,不至于吧,这都是用啥加密的。。。

就一个base64编码
5#
 楼主| littlewhite11 发表于 2024-11-30 13:09 |楼主
YIUA 发表于 2024-11-30 13:07
老哥你是真的高产

没有没有,巩固知识
6#
edgrdg 发表于 2024-11-30 13:13
用心讨论,共获提升!
7#
Knm 发表于 2024-11-30 13:22
感谢大佬分享
8#
CLYLR 发表于 2024-11-30 13:34
感谢大佬分享 很厉害!
9#
Reze 发表于 2024-11-30 13:34
感谢大佬分享的教程
10#
zfb38 发表于 2024-11-30 13:48
解决问题的技术
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-14 18:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表