案例目标

网址:第七题 动态字体,随风漂移 - 猿人学

本题目标:采集 5 页中胜点列的数据,找出胜点最高的召唤师,将召唤师姓名填入答案中

字体反爬

演变的基本阶段:

1. 固定字体编码、固定字体坐标

2. 动态字体编码、固定字体坐标

3. 动态字体编码、动态字体坐标

字体反爬的原理:前端工程师通过自定义的字体来替换页面中某些关键的数据,在 HTML 中使用 @font-face 自定义字体:

@font-face {font-family: <identifier>; src: <fontsrc> [, <fontsrc>]*; <font>;}

里面的 font-family 也就是一个特定的名字,src 就表示需要引用的具体的文件,而这个文件就是字体文件,一般是 ttf 类型,eot 类型,现在因为 ttf 类型文件过大,在移动端使用的时候会导致加载速度过慢,woff 类型的文件较为广泛应用。

更多相关推荐阅读:谈谈字体反爬的前世今生

常规 JavaScript 逆向思路

一般情况下,JavaScript 逆向分为三步:

  • 寻找入口:逆向在大部分情况下就是找一些加密参数到底是怎么来的,关键逻辑可能写在某个关键的方法或者隐藏在某个关键的变量里,一个网站可能加载了很多 JavaScript 文件,如何从这么多的 JavaScript 文件的代码行中找到关键的位置,很重要;
  • 调试分析:找到入口后,我们定位到某个参数可能是在某个方法中执行的了,那么里面的逻辑是怎么样的,调用了多少加密算法,经过了多少赋值变换,需要把整体思路整理清楚,以便于断点或反混淆工具等进行调试分析;
  • 模拟执行:经过调试分析后,差不多弄清了逻辑,就需要对加密过程进行逻辑复现,以拿到最后我们想要的数据

接下来开始正式进行案例分析:

寻找入口

F12 打开开发者人员工具,刷新网页进行抓包,在 Network 中可以看到数据接口为 7,响应预览中可以看到当前页面各玩家胜点数据被混淆了:

最底下的部分为 woff 文件:

woff: "AAEAAAAKAIAAAwAgT1MvMvz1V98AAAEoAAAAYGNtYXAXunFWAAABpAAAAYpnbHlmG6nCuAAAA0gAAAQEaGVhZBpe3c8AAACsAAAANmhoZWEGwgFBAAAA5AAAACRobXR4ArwAAAAAAYgAAAAabG9jYQVeBnQAAAMwAAAAGG1heHABGABFAAABCAAAACBuYW1lUGhGMAAAB0wAAAJzcG9zdCztalcAAAnAAAAAiAABAAAAAQAA9ZRArF8PPPUACQPoAAAAANnIUd8AAAAA3ztIRgAG/+wCOALZAAAACAACAAAAAAAAAAEAAAQk/qwAfgJYAAAAOgIeAAEAAAAAAAAAAAAAAAAAAAACAAEAAAALADkAAwAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAIqAZAABQAIAtED0wAAAMQC0QPTAAACoABEAWkAAAIABQMAAAAAAAAAAAAAEAAAAAAAAAAAAAAAUGZFZABAoWnnUQQk/qwAfgQkAVQAAAABAAAAAAAAAAAAAAAgAAAAZAAAAlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAIQAAwABAAAAHAAEAGgAAAAWABAAAwAGoWmlc6YZtHi1Y7h2xBflluYk51H//wAAoWmlc6YZtHi1Y7h2xBflluYk51H//16hWpNZ8EuNSqRHizvtGmwZ3xi3AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwBDAJoA2wDxAQ4BTAFfAZAB0wICAAEABv/sADAAEgACAAA3MxUGKhImAAABACv/8gIsAsUAJAAAEwMzNjc2MxYWFRQGIyInJjUjBhcWMzI3NjU0JiMGBwYHIzchNXUyRBYuJjpCZ2k0RTspYANjLl58RD11ZD4OShUEFgGHAsX+cC8mDAlUYkJXFCk7WTgzTEFgcYMBCh0h6VIAAwAd//ICLALQAB8ALAA4AAABIgcGFRQXFhc1BgcGFRQWIDc2NTQnJicVNjc2NTQnJgcyFxYUBwYiJyY0NzYTMhcWFAcGIiY0NzYBK341NSYLMTIfN4UBHR5FLScwOR82VjR3awg2FUqhHRIQKkxPQRMTRppoKj4C0DYxUSpADSkJFiw7Tl9lNy5fTjssFgkpDUAqUTE2RCcbXDAhITBcGyf+xywdiyInSYsdLAAAAgAu//ICIALZABsAKAAAASIHBhUUFxYXNjY0JiMiBwYHIyc0NzY3FhUzNgM2FwYUBwYHIicmNDYBOX5DSkUyimKPUo49EUUfDgY9JFSfPQzeOVwEKDE4GHQIPwLQaFTPbH9nAQGH5m0iFUEjbE9YFRVjsf6xKFslmSE6DS0um2MAAAEAdQAAAXICxQAJAAABBgYHFTY3ETMRATk1Zil9JloCxQNiE1QlQv2gAsUAAgAaAAACOALFAAoADgAAAQEVIRUzNTM1IxEHMxEhAX/+mwFbcFNTcwP/AALF/jFTo6MzAe9g/nEAAAIALv/yAiAC0AAcACgAAAEiBhUUFxYzNjY3MxcUBwYjIjcjFjM2NzY1NCcmBzIXFhQGIyImNTQ2ARtkiT1CY0FtEAkOQwx7hA5vIsd7QkQ8RoRUKjZjUShkTQLQhl54QUEPMi0bekNXcbABZ3yio1JjMDg+mV5kVT13AAABADYAAAIsAsUABgAAExUhATMBNTYBif79ZQELAsVC/X0Cg0IAAAEAMQAAAiwC0AAdAAABIgYHMyY3NhcyFhUUBwYHBgcGFSE1ITY3Njc2NCYBOXRyHV8CJGoYNWlBLVRoH10B2v6RF4FvJmOWAtCGfFw+OAhFQjVbITlGP0haP1xSTB5brnAAAQAu//ICLALQACsAAAEiBwYHMzY2NxYXFhQGIyMVMzYWFAcGIyYnJjcjFhcWFzI2NTQnJic2NTQmATluPTwcXxB8GDQ1KFk4R0c+XQZdP1ArOAVWJS9KZmaIKBNIj44C0DY6Z0VaCAgzG25TQwhBgS8fCg8vSm0oSAFvbEIqGyQqZoNFAAACADD/8gIgAtkADAAZAAABJgcGEBcWMjc2ECcmBzIXFhAHBiInJhA3NgErb1M5OVPgSDw8SHFZMBYWMLgfHx8fAtAJeEf+v39oaH8BQUd4QVxQ/u4/U1M/ARJQXAAAAAASAN4AAQAAAAAAAAAXAAAAAQAAAAAAAQAMABcAAQAAAAAAAgAHACMAAQAAAAAAAwAUACoAAQAAAAAABAAUACoAAQAAAAAABQALAD4AAQAAAAAABgAUACoAAQAAAAAACgArAEkAAQAAAAAACwATAHQAAwABBAkAAAAuAIcAAwABBAkAAQAYALUAAwABBAkAAgAOAM0AAwABBAkAAwAoANsAAwABBAkABAAoANsAAwABBAkABQAWAQMAAwABBAkABgAoANsAAwABBAkACgBWARkAAwABBAkACwAmAW9DcmVhdGVkIGJ5IGZvbnQtY2Fycmllci5QaW5nRmFuZyBTQ1JlZ3VsYXIuUGluZ0ZhbmctU0MtUmVndWxhclZlcnNpb24gMS4wR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwByAGUAYQB0AGUAZAAgAGIAeQAgAGYAbwBuAHQALQBjAGEAcgByAGkAZQByAC4AUABpAG4AZwBGAGEAbgBnACAAUwBDAFIAZQBnAHUAbABhAHIALgBQAGkAbgBnAEYAYQBuAGcALQBTAEMALQBSAGUAZwB1AGwAYQByAFYAZQByAHMAaQBvAG4AIAAxAC4AMABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAIAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAsACwAAAQoBCAEJAQIBCwEDAQUBBwEEAQYHdW5pYzQxNwd1bmlhNTczB3VuaWE2MTkHdW5pYjU2Mwd1bmlhMTY5B3VuaWU3NTEHdW5pZTU5Ngd1bmllNjI0B3VuaWI4NzYHdW5pYjQ3OA=="

woff 文件是字体文件,实际上就是编码和字符的映射表,如 &#xc134,&#x 是字符前缀,c134 是字符对应的编码,本题字体文件和胜点列的数据都在一个接口里,从 Initiator 中向下跟栈到 request 对应的 7:formatted 文件中:

ctrl + f 局部搜索 woff 关键字,有一个结果,在该文件的第 993 行

ttf = data.woff;
$('.font').text('').append('<style type="text/css">@font-face { font-family:"fonteditor";src: url(data:font/truetype;charset=utf-8;base64,' + ttf + '); }</style>');

src 为下载路径,这里可以看到 woff 文件被保存为了 ttf 格式,通过 python 将其下载下来:

import base64# 填入 woff 后字符串中的内容
woff_content = "AAEAA........jMjUx"
with open('./yrx7.ttf', 'wb') as f:f.write(base64.b64decode(woff_content))

调试分析

经过测试,字体编码是在动态变化的,所以无法直接对应替换:

import requestsheaders = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
}url = "https://match.yuanrenxue.com/api/match/7"for _ in range(3):response = requests.get(url, headers=headers)print(response.json()['data'][0])# {'value': '&#xc514 &#xc496 &#xc514 &#xa894 '}
# {'value': '&#xb287 &#xa568 &#xb287 &#xb127 '}
# {'value': '&#xb953 &#xb493 &#xb953 &#xe643 '}

所以我们需要通过观察字体文件的具体内容来寻找突破口,通过 fontTools 库将 ttf 格式文件转换为 xml 格式文件:

fontTools 官方文档:fontTools Docs — fontTools Documentation

Github fontTools:GitHub - fonttools/fonttools: A library to manipulate font files fromPython.

pip3 install fonttools
from fontTools.ttLib import TTFontfont = TTFont('./yrx7.ttf')
font.saveXML('./yrx7.xml')

先来分析下该 ttf 文件的结构:

from fontTools.ttLib import TTFontfont = TTFont("yrx7.ttf")
# 获取 ttf 文件中的表名
list_name = font.keys()
print(list_name)
# ['GlyphOrder', 'head', 'hhea', 'maxp', 'OS/2', 'hmtx', 'cmap', 'loca', 'glyf', 'name', 'post']

表名包含 glyf 关键字,表示图元数据,以序列形式保存,每个图元以图元头(GlyphHeader)结构开始,表明这是一个使用 TrueType 轮廓的字体文件 :

TrueType:Windows 和 Mac 系统最常用的字体格式,基于轮廓技术的数学模式来进行定义,比基于矢量的字体更容易处理,保证了屏幕与打印输出的一致性,同时,这类字体和矢量字体一样可以随意缩放、旋转而不必担心会出现锯齿。

TTGlyph 的两个子类 TTGlyphGlyf 和 _TTGlyphCFF 分别对应 TrueType 轮廓和 Postscript 轮廓:

from fontTools.ttLib import TTFontfont = TTFont("xxx.ttf")
glyph = font.getGlyphSet()["uni70E0"] # 获取 _TTGlyph 实例
print(glyph._glyph.coordinates) # 坐标
print(glyph._glyph.endPtsOfContours) # 轮廓结束点
print(list(glyph._glyph.flags)) # 点类型 flag

生成的 xml 文件对应的字体编码部分如下:

  <GlyphOrder><!-- The 'id' attribute is only for humans; it is ignored when parsed. --><GlyphID id="0" name=".notdef"/><GlyphID id="1" name="unif472"/><GlyphID id="2" name="unif865"/><GlyphID id="3" name="unic846"/><GlyphID id="4" name="unic251"/><GlyphID id="5" name="unif165"/><GlyphID id="6" name="unib294"/><GlyphID id="7" name="unib721"/><GlyphID id="8" name="unic215"/><GlyphID id="9" name="unib241"/><GlyphID id="10" name="unie291"/></GlyphOrder>

字体编码为 unic846  的字体坐标:

刷新网页获取新的 woff 值,再下载保存一份 ttf 文件作对比,会发现字体坐标 x、y 是动态变化的,但是 on 的值是不变的:

通过比较,其他字符的 on 值也不变,所以可以用过 on 值形成映射关系从而进行识别,将下载后的 ttf 文件放到 在线字体编辑器-FontStore 中打开,这里还原出了每个加密编码对应的数字字符:

根据 ttf 文件编码顺序对应更改,我的 ttf 文件对应的数字顺序为:6, 1, 3, 2, 5, 7, 4, 9, 0, 8 ,接下来获取每个坐标对应的 on 值:

方式一:

from fontTools.ttLib import TTFont# 创建 TTFont 实例
font = TTFont("yrx7.ttf")
# 返回一个 _TTGlyphset 对象, 包含字形轮廓数据
# _TTGlyphset 是一个类似字典的, 以字形名称为键、_TTGlyph 为值的对象
glyf = font.getGlyphSet()
# 返回一个字形名称列表, 以字母顺序排序
glyphNames = font.getGlyphNames()# 数字列表
num_list = [6, 1, 3, 2, 5, 7, 4, 9, 0, 8]
map_dict= {}for i, name in enumerate(glyphNames[1:]):# 通过字形名称选择字形对象g = glyf[name]# 点类型 flag# 读取每个坐标对应的 on 值flag = list(g._glyph.flags)# 控制点的坐标# print(glyph._glyph.coordinates)# map_dict 字典里的键对应的数字,值则是 on 值构成的列表map_dict[num_list[i]] = flagprint(map_dict)

方式二:

on_dict = {}
num_list = [6, 1, 3, 2, 5, 7, 4, 9, 0, 8]
# 创建 TTFont 实例
font = TTFont("yrx7.ttf")
# 返回一个字形名称列表,以字母顺序排序
glyphNames = font.getGlyphNames()
for i, name in enumerate(glyphNames[1:]):# 读取 glyf 表, 获取各字形对应的 on 值on_data = font['glyf'][name].flagson_key = "".join([str(n) for n in on_data])on_dict[on_key] = num_list[i]
print(on_dict)

接下来需要知道各胜点是如何赋值给对应玩家的,相关计算逻辑同样在 7 文件中,在格式化后的第 1008 行,打下断点进行调试:

name[yyq + (window.page - 1) * 10]

Python 代码

get_on_map() 函数的 num_list 列表中的数字顺序需要与保存的 ttf 文件中的编码顺序一致 ,sessionid 要改为自己的:

import base64import requests
from fontTools.ttLib import TTFont# 玩家名称列表
player_list = ['极镀ギ紬荕', '爷灬霸气傀儡', '梦战苍穹', '傲世哥', 'мaη肆風聲', '一刀メ隔世', '横刀メ绝杀', 'Q不死你R死你', '魔帝殤邪', '封刀不再战', '倾城孤狼', '戎马江湖','狂得像风', '影之哀伤', '謸氕づ独尊', '傲视狂杀','追风之梦', '枭雄在世', '傲视之巅', '黑夜刺客', '占你心为王', '爷来取你狗命', '御风踏血', '凫矢暮城','孤影メ残刀', '野区霸王', '噬血啸月', '风逝无迹', '帅的睡不着', '血色杀戮者', '冷视天下', '帅出新高度','風狆瑬蒗', '灵魂禁锢', 'ヤ地狱篮枫ゞ', '溅血メ破天', '剑尊メ杀戮', '塞外う飛龍', '哥‘K纯帅','逆風祈雨','恣意踏江山', '望断、天涯路', '地獄惡灵', '疯狂メ孽杀', '寂月灭影', '骚年霸称帝王', '狂杀メ无赦','死灵的哀伤','撩妹界扛把子', '霸刀☆藐视天下', '潇洒又能打', '狂卩龙灬巅丷峰', '羁旅天涯.', '南宫沐风', '风恋绝尘','剑下孤魂', '一蓑烟雨', '领域★倾战', '威龙丶断魂神狙', '辉煌战绩', '屎来运赚', '伱、Bu够档次','九音引魂箫','骨子里的傲气', '霸海断长空', '没枪也很狂', '死魂★之灵']def get_on_map():on_dict = {}# 根据 ttf 文件编码顺序对应更改num_list = [6, 1, 3, 2, 5, 7, 4, 9, 0, 8]# 创建 TTFont 实例font = TTFont('yrx7.ttf')# 返回一个字形名称列表,以字母顺序排序glyphNames = font.getGlyphNames()for i, name in enumerate(glyphNames[1:]):# 读取 glyf 表, 获取各字形对应的 on 值on_data = font['glyf'][name].flagson_key = "".join([str(n) for n in on_data])on_dict[on_key] = num_list[i]return on_dictdef parse_ttf(woff):map_dict = {}woff_decode = base64.b64decode(woff)with open('match7.ttf', 'wb') as f:f.write(woff_decode)# 创建 TTFont 实例font = TTFont('match7.ttf')font.saveXML('match7.xml')# 返回一个字形名称列表,以字母顺序排序glyphNames = font.getGlyphNames()# 第一个是 .notdeffor name in glyphNames[1:]:# 读取 glyf 表, 获取各字形对应的 on 值on_data = font['glyf'][name].flagson_key = "".join([str(n) for n in on_data])map_dict[name.replace('uni', '&#x')] = get_on_map()[on_key]return map_dictdef main():player_dict = {}num_list = []headers = {'user-agent': 'yuanrenxue.project'}cookies = {'sessionid': ' your sessionid '}for page in range(1, 6):url = "https://match.yuanrenxue.com/api/match/7?page=%s" % pageresponse = requests.get(url, headers=headers, cookies=cookies)# 获取 woff 值woff = response.json()['woff']# 获取数据value_list = response.json()['data']# 解析 woffwoff_dict = parse_ttf(woff)for index, value in enumerate(value_list):# 解析获取胜点值win_point = value['value'].split(' ')[:-1]# 将经过编码的字符转为数字num = "".join([str(woff_dict[i]) for i in win_point])# 玩家名称对应的值player = player_list[(index + 1) + (page - 1) * 10]player_dict[num] = playernum_list.append(num)print("最强玩家: " + player_dict[max(num_list)] + " ---> 胜点: " + max(num_list))if __name__ == '__main__':main()

TTFont 相关方法:

参考链接:fontTools-字体文件的解析

font.getGlyphOrder() # 返回一个字形名称列表,以其在文件中的顺序排序
font.getGlyphNames() # 返回一个字形名称列表,以字母顺序排序
font.getBestCmap() # 返回一个字形 ID 为键、字形名称为值的字典
font.getReverseGlyphMap() # 返回一个字形名称为键、字形 ID 为值的字典
font.getGlyphName(10000) # 输入字形 ID 返回字形名称
font.getGlyphID("uni70E0") # 输入字形名称返回字形 ID
font.getGlyphSet() # 返回一个 \_TTGlyphSet 对象,包含字形轮廓数据

【JavaScript 逆向】猿人学 web 第七题:动态字体,随风漂移相关推荐

  1. JS逆向实战17——猿人学第七题 动态字体 随风漂移

    声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 网站 第七题 动态字体,随 ...

  2. 猿人学web端爬虫攻防平台第七题动态字体

    本人呢很喜欢爬虫,但是技术不好,所以呢就一直慢慢的摸索着,也不知道那什么练手进行学习,直到我在偶然的机会接触到了猿人学,发现了 猿人学web端爬虫攻防平台这个网站,久旱逢甘霖,他乡遇故知呀(蹩脚的词语 ...

  3. 【JavaScript 逆向】猿人学 web 第五题:乱码增强

    案例目标 网址:第五题 js 混淆 乱码增强 - 猿人学 本题目标:抓取全部 5 页直播间热度,计算前 5 名直播间热度的加和 常规 JavaScript 逆向思路 一般情况下,JavaScript ...

  4. 【JavaScript 逆向】猿人学 web 第十八题:jsvmp,洞察先机

    案例目标 网址:第十八题 jsvmp 洞察先机 - 猿人学 本题目标:抓取 5 页数字,计算加和并提交结果 常规 JavaScript 逆向思路 一般情况下,JavaScript 逆向分为三步: 寻找 ...

  5. 猿人学web端爬虫攻防大赛赛题解析_第九题:js混淆-动态cookie2

    js混淆,动态cookie2 一.前言 二.加密逻辑初探 三.加密逻辑深入分析 四.代码实现 4.1.ast解混淆的一个坑 4.2.完整实现过程 五.参考文献 一.前言 一转眼又有快两个星期没更博客了 ...

  6. js逆向-猿人学(7-8)动态字体-图文点选

    猿人学爬虫比赛第七题:<动态字体,随风漂移> 地址: http://match.yuanrenxue.com/match/7 召唤师名字是在html 的 script 标签中,字体是从接口 ...

  7. 猿人学web端爬虫攻防大赛赛题解析_第一题:源码乱码

    第一题:js 混淆 - 源码乱码 1.前言 2.题目理解 3.逆向(踩坑)分析过程 3.1.初步分析 3.2.当头一棒 3.3.循序渐进 3.4.大功告成 4.结语 1.前言 接触网络爬虫已经有两三年 ...

  8. 猿人学web端爬虫攻防大赛赛题解析_第四题:雪碧图、样式干扰

    第四题:雪碧图 - 样式干扰 1.前言 2.题目理解 3.解析过程 3.1.初窥门径 3.2.深入探究 3.2.1.确定原理 3.2.2.逆向破解 3.2.代码实现 4.结语 1.前言 久违的第四题终 ...

  9. 【猿人学WEB题目专解】猿人学第5题

最新文章

  1. 2019年最新最全运维技能图谱
  2. Hive mac 客户端工具DbVisualizer配置
  3. python常用英文单词怎么写,Python常用英文单词
  4. 备忘录 - FL2440 开发板操作要点及注意事项
  5. 计算机操作员理论知识试题,计算机操作员理论考试试题.doc
  6. shell实战训练营Day14
  7. 领域驱动设计 软件核心复杂性应对之道_DDD - 领域驱动设计对软件复杂度的应对(上)...
  8. 软件测试的工作内容主要是干什么?
  9. Oracle 月初、月末等时间获取示例
  10. Java中static、this、super、final用法
  11. IBM TSM 6.3学习笔记(1)
  12. ThinkPHP5.1.x 框架源码分析之框架的灵魂
  13. 基于Python的头脑王者脚本(纯娱乐)
  14. springboot-加载自定义的properties文件
  15. codeforces 1255 B. Fridge Lockers
  16. 尹成学院区块链 Go 学习大纲-取得大纲试看视频联系微信yinchengak48
  17. MySQL基本操作—DQL实训真题
  18. 网络安全进阶篇之流量加密(十三章-3)CS生成ssl证书修改c2 profile 加密流量逃逸检测
  19. 一次性删除页面中所有console.log()
  20. fluent怎么用c语言编程,[转载]FLUENT UDF 使用指导

热门文章

  1. 你不甘堕落、却又不思进取
  2. 如何将footer标签固定在底部_如何让网页的footer一直固定在底端
  3. Javascript调用事件方法
  4. C语言 ACLLib键盘方向键控制图形移动以及鼠标控制图形移动
  5. godot3.4节点按照键盘方向键旋转和移动
  6. Writing a Scientific Research Report (IMRaD)学习笔记
  7. 直博与硕博连读的优劣
  8. 贪心算法实现活动安排问题
  9. FineBI-行式填报
  10. oracle NVL和NVL2区别