整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

什么是UA(User-Agent)识别系统?

什么是UA(User-Agent)识别系统?

A(User-Agent)是一个特殊字符抬头,通过服务器进行识别应用的操作系统、版本类别及标识、CPU型号、浏览器内核、硬件信息及相关型号、浏览器渲染引擎、浏览器语言等。

通常网站会通过 UA(User-Agent)来给不同的操作系统、不同的浏览器发送不同的页面,识别这些内容就需要UA识别系统来进行。

UA字串的标准格式:浏览器标识(包括:操作系统标识;加密等级标识;浏览器语言)渲染引擎标识 版本信息

API接口:根据用户user-agent信息识别操作系统、浏览器信息、浏览器内核、硬件信息及相关型号,识别率达90%以上

后附:user-agent(UA)识别Api接口代码:

Api文档:

子接口:

  • 手机电脑设备型号识别

接口地址: https://登录后显示/njs/167/319(支持:http/https)

返回格式:json,xml

请求方式:GET,POST

POST 请求需要设置Header头:Content-Type: application/x-www-form-urlencoded;charset=utf-8

请求说明:


Md5验证方式-加密顺序:

返回参数说明:

名称

必填

类型

说明

示例 参数另存

alias

String


7

browser

Array



camouflage

Boolean



channel

String


Nightly

codeid

Integer

状态码,返回10000状态都会进行计费。具体说明可查看状态码说明

10000

curtime

String

当前服务器时间戳

1633915763

detectCamouflage

Boolean


1

device

Array



engine

Array



features

Array



hidden

Boolean



identified

Boolean



message

String

请求状态说明

返回成功

name

String

姓名

Windows

options

Array



original

String


6.1

os

Array



retdata

Array

回数据集合,可能是数据、对象或者字符串


stock

Boolean



type

String


desktop

useFeatures

Boolean



version

Array



JSON返回示例:


状态码说明:

状态码

说明

10000

返回成功

10001

appid必须指定,可以我的应用里面查看

10002

sign值必须指定,加密规则请前往帮助中心查看

10003

sign值验证不通过,加密规则请前往帮助中心查看

10004

时差不能超过10分钟,可以不传递这个参数,注意时间戳单位是秒

10005

appid错误,请检查appid值,前往会员中心->我的应用查看或添加

10006

当前IP地址未授权,请前往用户中心->我的应用添加ip{@info}

10007

应用被禁用,请联系客服处理

10008

应用内没有该接口,请到我的应用里面添加这个接口

10009

api接口不存在

10010

您没有添加该api接口

10011

api已经到期

10012

没有订购任何api,请前往购买后再操作

10013

该接口已经暂停使用

10014

未知的错误,可以联系客服处理

10015

参数个数错误

10019

{@info}

10017

time必须是整型

10018

次数不足

10020

子接口不存在,可能已经被关闭

10021

服务器发生错误

10022

帐户余额不足,请充值!

10023

订单提交成功,等待回调结果

10024

调试模式数据

10025

查无数据

请求示例:



以下内容转载自:http://madong.net.cn/index.php/2016/05/557/
(这个文章对UA系统的以生动的故事方式写得非常明白、透澈,借来说明一下枯燥的代码用途。)

很久很久以前有一个浏览器名字叫 NCSA Mosaic

很久很久以前有一个浏览器名字叫 NCSA Mosaic

紧接着也出现了一个 Mozilla 的浏览器 (Mozilla 的意思是 Mosaic 终结者)
后来 Mozilla 的正式发布版本是 Netscape 它把自己标称为Mozilla/1.0 (Win3.1)
由于 Netscape支持框架显示,后来框架在大家中间流行起来了,但Mosaic不支持框架。
所以网站管理员们则通过 User-Agent 判断,如果是Netscape浏览器则进入框架(html frame)的页面,如果不是Netscape 浏览器则进入没有框架的页面.

Netscape 没有风光多久,微软也推出了自己的 IE浏览器 。IE浏览器也支持 框架 但是很遗憾,网站管理员们不认识它呀,因为IE的 User-Agent 没有Mozilla 所有没有人理它。后来微软抓狂了,你们不就是只认识User-Agent 头里有 Mozilla 字符的浏览器么,于是宣称自己是“兼容Mozilla”的,开始模仿Netscape,把自己标称为Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)。这样一来 IE 也有了Mozilla (其实是伪装的…) 所以那些框架也能看到了.

后来随着微软把浏览器捆绑进自己的操作系统里一起卖,随就爆发了浏览器大战。结果大家都很清楚,Netscape失败了。而微软大胜了,至今微软的IE依然影响着 w3c 影响着所有浏览器。

Netscape 失败后重生为Mozilla 构造了Gecko,标称其为Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.1) Gecko/20020826,Gecko属于渲染引擎,表现优异。Mozilla开发了Firefox,标称为Mozilla/5.0 (Windows; U; Windows NT 5.1; sv-SE; rv:1.7.5) Gecko/20041108 Firefox/1.0,并且Firefox表现也非常优秀.

由于 Gecko 的优秀,网站管理员们会判断浏览器是否是 Gecko 的,如果是则把更先进 更漂亮页面显示给这个浏览器,其他浏览器就没有这个待遇了。

(比如现在的HTML5 , 如果我判断是你firefox的新版本则跳转到地址1,如果是ie则去地址2)

看到 Gecko 能看到这么漂亮的页面 Linux 的平台的孩子们很桑心,因为他们创建了基于KHTML引擎支持的Konqueror也跟 Gecko 一样优秀,但却不带有Gecko而被识别。结果Konquerer开始伪装自己“像Gecko”那样以看到更漂亮的网页,并标称自己为 Mozilla/5.0 (compatible; Konqueror/3.2; FreeBSD)(KHTML, like Gecko),这个世界就抽风了….

Apple开发了Safari,使用了KHTML,同时也增加了很多新特性,后来另起炉灶叫了WebKit,但是它有希望能够看到那些为KHTML编写的网页,于是Safari标称自己为Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5,就更加混乱了.

Google也开发了自己的浏览器Chrome,使用了Webkit,有点像Safari,希望能看到为Safari编写的网页,于是决定装成Safari。Chrome使用了WebKit渲染引擎,想装成Safari,Chrome宣称自己是Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko)Chrome/0.2.149.27 Safari/525.13

最后:

IE伪装成 Mozilla
webKit 伪装成 KHTML
KHTML 伪装成 Gecko

最后 opera 伪装成上面任何浏览器

同时所有的浏览器又都宣称自己是 Mozilla

般来说,识别navigator.userAgent即可识别出用户所使用的浏览器,但是UA是可以伪造的。

我们的思路是根据各个浏览器的独特功能来识别浏览器,比如:

if(window.ActiveXObject){
 //IE6或更低版本
}

以上代码即可识别IE浏览器。

本文由ISMY博客发布

转载请注明出处。

注微信公众号:K哥爬虫,持续分享爬虫进阶、JS/安卓逆向等技术干货!

声明

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

逆向目标

  • 目标:W 店登录接口 UA 参数加密,JS 代码经过了 OB 混淆
  • 主页:aHR0cHM6Ly9kLndlaWRpYW4uY29tLw==
  • 接口:aHR0cHM6Ly9zc28xLndlaWRpYW4uY29tL3VzZXIvbG9naW4=
  • 逆向参数:Form Data:ua: H4sIAAAAAAAAA91ViZUbMQhtiVOIcnRRxRafr%2FGuN5ukgoyfLUZC...

OB 混淆简介

OB 混淆全称 Obfuscator,Obfuscator 其实就是混淆的意思,官网:https://obfuscator.io/ ,其作者是一位叫 Timofey Kachalov 的俄罗斯 JavaScript 开发工程师,早在 2016 年就发布了第一个版本。

一段正常的代码如下:

function hi() {
  console.log("Hello World!");
}
hi();

经过 OB 混淆后的结果:

function _0x3f26() {
    var _0x2dad75=['5881925kTCKCP', 'Hello\x20World!', '600mDvfGa', '699564jYNxbu', '1083271cEvuvT', 'log', '18sKjcFY', '214857eMgFSU', '77856FUKcuE', '736425OzpdFI', '737172JqcGMg'];
    _0x3f26=function () {
        return _0x2dad75;
    };
    return _0x3f26();
}

(function (_0x307c88, _0x4f8223) {
    var _0x32807d=_0x1fe9, _0x330c58=_0x307c88();
    while (!![]) {
        try {
            var _0x5d6354=parseInt(_0x32807d(0x6f)) / 0x1 + parseInt(_0x32807d(0x6e)) / 0x2 + parseInt(_0x32807d(0x70)) / 0x3 + -parseInt(_0x32807d(0x69)) / 0x4 + parseInt(_0x32807d(0x71)) / 0x5 + parseInt(_0x32807d(0x6c)) / 0x6 * (parseInt(_0x32807d(0x6a)) / 0x7) + -parseInt(_0x32807d(0x73)) / 0x8 * (parseInt(_0x32807d(0x6d)) / 0x9);
            if (_0x5d6354===_0x4f8223) break; else _0x330c58['push'](_0x330c58['shift']());
        } catch (_0x3f18e4) {
            _0x330c58['push'](_0x330c58['shift']());
        }
    }
}(_0x3f26, 0xaa023));

function _0x1fe9(_0xa907e7, _0x410a46) {
    var _0x3f261f=_0x3f26();
    return _0x1fe9=function (_0x1fe950, _0x5a08da) {
        _0x1fe950=_0x1fe950 - 0x69;
        var _0x82a06=_0x3f261f[_0x1fe950];
        return _0x82a06;
    }, _0x1fe9(_0xa907e7, _0x410a46);
}

function hi() {
    var _0x12a222=_0x1fe9;
    console[_0x12a222(0x6b)](_0x12a222(0x72));
}

hi();

OB 混淆具有以下特征:

1、一般由一个大数组或者含有大数组的函数、一个自执行函数、解密函数和加密后的函数四部分组成;

2、函数名和变量名通常以 _0x 或者 0x 开头,后接 1~6 位数字或字母组合;

3、自执行函数,进行移位操作,有明显的 push、shift 关键字;

例如在上面的例子中,_0x3f26() 方法就定义了一个大数组,自执行函数里有 push、shift 关键字,主要是对大数组进行移位操作,_0x1fe9() 就是解密函数,hi() 就是加密后的函数。

抓包分析

点击登陆抓包,可以看到有个 ua 参数,经过了加密,每次登陆是会改变的,如下图所示:

如果直接搜索 ua 的话,结果太多,不方便筛选,通过 XHR 断点比较容易找到加密的位置,如下图所示,最后提交的 r 参数包含 ua 值,往上找可以看到是 i 的值经过了 URL 编码,再往上看,i 的值通过 window.getUa() 获取,这个实际上是 uad.js 里面的一个匿名函数。

跟进到 uad.js,可以看到调用了 window[_0x4651('0x710')] 这个方法,最后返回的 _0x261229 就是加密后的 ua 值,用鼠标把类似 _0x4651('0x710')_0x4651('0x440') 的值选中,可以看到实际上是一些字符串,这些字符串通过直接搜索,可以发现是在头部的一个大数组里,如下图所示:

混淆还原与替换

一个大数组,一个有明显的 push、shift 关键字的进行移位操作的自执行函数,是 OB 混淆无疑了,那么我们应该怎样去处理,让其看起来更顺眼一些呢?

你可以手动在浏览器选中查看值,在本地去替换,当然不用全部去替换,跟栈走,用到的地方替换就行了,不要傻傻的全部去挨个手动替换,这种方法适用于不太复杂的代码。

如果遇到代码很多的情况,建议使用反混淆工具去处理,这里推荐国内的猿人学OB混淆专解工具和国外的 de4js,猿人学的工具还原程度很高,但是部分 OB 混淆还原后运行会报错,实测本案例的 OB 混淆经过猿人学的工具处理后就不能正常运行,可能需要自己预先处理一下才行,de4js 这个工具是越南的一个作者开发的,开源的,你可以部署到自己的机器上,它支持多种混淆还原,包括 Eval、OB、JSFuck、AA、JJ 等,可以直接粘贴代码,自动识别混淆方式,本案例推荐使用 de4js,如下图所示:

我们将还原后的结果复制到本地文件,使用 Fiddler 的 Autoresponder 功能对响应进行替换,如下图所示:

如果此时开启抓包,刷新页面,你会发现请求状态 status 显示的是 CORS error,JS 替换不成功,在控制台里还可以看到报错 No 'Access-Control-Allow-Origin' header is present on the requested resource. 如下图所示:

CORS 跨域错误

CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个 W3C 标准,该标准使用附加的 HTTP 头来告诉浏览器,允许运行在一个源上的 Web 应用访问位于另一不同源的资源。一个请求 URL 的协议、域名、端口三者之间任意与当前页面地址不同即为跨域。常见的跨域问题就是浏览器提示在 A 域名下不可以访问 B 域名的 API,有关 CORS 的进一步理解,可以参考 W3C CORS Enabled。

简要流程如下:

1、消费者发送一个 Origin 报头到提供者端:Origin: http://www.site.com; 2、提供者发送一个 Access-Control-Allow-Origin 响应报头给消费者,如果值为 * 或 Origin 对应的站点,则表示允许共享资源给消费者,如果值为 null 或者不存在,则表示不允许共享资源给消费者; 3、除了 Access-Control-Allow-Origin 以外,部分站点还有可能检测 Access-Control-Allow-Credentials,为 true 表示允许; 4、浏览器根据提供者的响应报文判断是否允许消费者跨域访问到提供者源;

我们根据前面在控制台的报错信息,可以知道是响应头缺少 Access-Control-Allow-Origin 导致的,在 Fiddler 里面有两种方法为响应头添加此参数,下面分别介绍一下:

第一种是利用 Fiddler 的 Filter 功能,在 Response Headers 里设置即可,分别填入 Access-Control-Allow-Origin 和允许的域名,如下图所示:

第二种是修改 CustomRules.js 文件,依次选择 Rules —> Customize Rules,在 static function OnBeforeResponse(oSession: Session) 模块下增加以下代码:

if(oSession.uriContains("要处理的 URL")){
    oSession.oResponse["Access-Control-Allow-Origin"]="允许的域名";
}

两种方法二选一,设置完毕后,就可以成功替换了,刷新再次调试就可以看到是还原后的 JS 了,如下图所示:

逆向分析

很明显 window.getUa 是主要的加密函数,所以我们先来分析一下这个函数:

window.getUa=function() {
    var _0x7dfc34=new Date().getTime();
    if (_0x4a9622) {
        _0x2644f4();
    }
    _0x55b608();
    var _0x261229=_0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x420004) + '|' + _0x7dfc34.toString(0x10);
    _0x261229=btoa(_0x570bef.gzip(_0x261229, {
        'to': 'string'
    }));
    return _0x261229;
};

_0x7dfc34 是时间戳,接着一个 if 判断,我们可以鼠标放到判断里去看看,发现判断的 _0x4a9622 是 false,那么 _0x2644f4() 就不会被执行,然后执行了 _0x55b608() 方法,_0x261229 的值,主要调用了 _0x1722c3() 方法得到的,前后依次传入了 _0x2e98dd_0x420004,很明显这两个值比较关键,分别搜索一下,可以发现:

_0x2e98dd 定义了一些 header、浏览器的信息、屏幕信息、系统字体信息等,这些信息可以作为定值直接传入,如下图所示:

_0x420004 搜索有用的结果就是仅定义了一个空对象,在控制台输出一下可以看到实际上包含了一些键盘、鼠标点击移动的数据,实际上经过测试发现, _0x420004 的值并不是强校验的,可以使用随机数模拟生成,也可以直接复制一个定值。

_0x2e98dd_0x420004 这两个参数都没有进行强校验,完全可以以定值的方式传入,这两个值都是 JSON 格式,我们可以直接在控制台使用 copy 语句复制其值,或者使用 JSON.stringify() 语句输出结果再手动复制。

本地联调

里面各个函数相互调用,比较多,可以直接把整个 JS copy 下来,我们注意到整个函数是一个自执行函数,在本地调用时,我们可以定义一个全局变量,然后在 window.getUa 函数里,将 _0x261229 的值赋值给全局变量,也就相当于导出值,最后取这个全局变量即可,还有一种方法就是不让它自执行,改写成正常一般的函数,然后调用 window.getUa 方法得到 ua 值。

首先我们把 _0x2e98dd_0x420004 的值在本地定义一下,这里有个小细节,需要把原 JS 代码里这两个值定义的地方注释掉,防止起冲突。

在本地调试时,会提示 windowlocationdocument 未定义,定义一下为空对象即可,然后又提示 attachEvent 未定义,搜索一下,是 _0x13cd5a 的一个原型对象,除了 attachEvent 以外,还有个 addEventListeneraddEventListener() 方法用于向指定元素添加事件句柄,在 IE 中使用 attachEvent() 方法来实现,我们在 Google Chrome 里面埋下断点调试一下,刷新页面会直接进入 addEventListener() 方法,其中的事件是 keydown,即键盘按下,就调用后面的 _0x5cec90 方法,输出一下后面返回的 this,实际上并没有产生什么有用的值,所以 _0x13cd5a.prototype.bind 方法我们可以直接将其注释掉,实际测试也没有影响。

接着本地调试,又会提示 btoa 未定义,btoaatob 是 window 对象的两个函数,其中 btoa 是 binary to ascii,用于将 binary 的数据用 ascii 码表示,即 Base64 的编码过程,而 atob 则是 ascii to binary,用于将 ascii 码解析成 binary 数据,即 Base64 的解码过程。

在 NodeJS 里,提供了一个称为 Buffer 的本地模块,可用于执行 Base64 编码和解码,这里不做详细介绍,可自行百度,window.getUa 方法里的原 btoa 语句是这样的:

_0x261229=btoa(_0x570bef.gzip(_0x261229, {'to': 'string'}));

在 NodeJS 里,我们可以这样写:

_0x261229=Buffer.from(_0x570bef.gzip(_0x261229, {'to': 'string'}), "latin1").toString('base64');

注意:Buffer.from() 传入了一个 latin1 参数,这是由于 _0x570bef.gzip(_0x261229, {'to': 'string'}) 的结果是 Latin1(ISO-8859-1 的别名)编码,如果不传,或者传入其他参数,则最终结果可能和 btoa 方法得出的结果不一样!

自此,本地联调完毕,就可以得到正确的 ua 值了!

完整代码

GitHub 关注 K 哥爬虫,持续分享爬虫相关代码!欢迎 star !https://github.com/kgepachong/

以下只演示部分关键代码,不能直接运行! 完整代码仓库地址:https://github.com/kgepachong/crawler/

JavaScript 加密关键代码架构

var window={};
var location={};
var document={};
var _0x5a577d=function () {}();
var _0xe26ae=function () {}();
var _0x3204b9=function () {}();
var _0x3c7e70=function () {}();
var _0x4a649b=function () {}();
var _0x21524f=function () {}();
var _0x2b0d61=function () {}();
var _0x53634a=function () {}();
var _0x570bef=function () {}();
var _0xd05c32=function (_0x5c6c0c) {};
window.CHLOROFP_STATUS='start';

// 此处省略 N 个函数

var _0x2e98dd={
    // 对象具体的值已省略
    "basic": {},
    "header": {},
    "navigator": {},
    "screenData": {},
    "sysfonts": [],
    "geoAndISP": {},
    "browserType": {},
    "performanceTiming": {},
    "canvasFp": {},
    "visTime": [],
    "other": {}
}
var _0x420004={
    // 对象具体的值已省略
    "keypress": true,
    "scroll": true,
    "click": true,
    "mousemove": true,
    "mousemoveData": [],
    "keypressData": [],
    "mouseclickData": [],
    "wheelDeltaData": []
}

window.getUa=function () {
    var _0x7dfc34=new Date().getTime();
    if (_0x4a9622) {
        _0x2644f4();
    }
    _0x55b608();
    var _0x261229=_0x1722c3(_0x2e98dd) + '|' + _0x1722c3(_0x420004) + '|' + _0x7dfc34.toString(0x10);
    // _0x261229=btoa(_0x570bef.gzip(_0x261229, {'to': 'string'}));
    _0x261229=Buffer.from(_0x570bef.gzip(_0x261229, {'to': 'string'}), "latin1").toString('base64');
    return _0x261229;
};

// 测试输出
// console.log(window.getUa())

Python 登录关键代码