JS 有什么手段可以判断一个函数是不是原生代码吗?

2021-05-08 16:22:07 +08:00
 xieqiqiang00

比如 XMLHttpRequest,有没有什么手段可以知道这个东西有没有被人为重写?
网站找了不少方法都做不到
下面是我做的一些尝试,这些检测方法都能被绕过

{
    function isNative(api) {
        return /native code/.test(api.toString()) && typeof api !== 'undefined'
    }

    let test = function (input, fake) {
        console.log("------------------------")
        console.log("是否是伪造:", fake)
        console.log("toString:", input.toString())
        console.log("toString.toString:", input.toString)
        console.log("prototype 方法", input.hasOwnProperty("prototype"))
        console.log("toString.call","方法",Function.prototype.toString.call(input))
        console.log("网传最不靠谱方法:isNative", isNative(input))
    }
    test(XMLHttpRequest, false)
    {
        let XMLHttpRequest = function () {
            "[native code]"
        }
        XMLHttpRequest.toString = function () {
            return "function XMLHttpRequest() { [native code] }"
        }
        let toString = function () {
            return "function toString() { [native code] }"
        }
        toString.toString = toString
        XMLHttpRequest.toString.toString = toString
        Function.prototype.toString = toString
        delete XMLHttpRequest.prototype
        test(XMLHttpRequest, true)
        // XMLHttpRequest.prototype = undefined
        // test(XMLHttpRequest, true)
    }
}
5873 次点击
所在节点    JavaScript
80 条回复
rekulas
2021-05-09 09:32:19 +08:00
我想到一种结合 valueof 的判断方式 ,大家考虑下如何可以绕过
```
function isNative(api) {
if (typeof XMLHttpRequest != 'function') return false;
if (typeof XMLHttpRequest.valueOf != 'function' ||
XMLHttpRequest.valueOf().toString() != 'function XMLHttpRequest() { [native code] }') {
return false;
}
return /native code/.test(api.toString()) && typeof api !== 'undefined'
}

console.error(isNative(XMLHttpRequest))
```
不过最终始终有疑问,如果客户端可以针对性劫持的话,那无论怎么写代码都无法判断的,客户端只需要在判断函数 return true 就行了
musi
2021-05-09 10:36:52 +08:00
@rekulas 我直接在这个帖子开启 console,把你的代码粘贴运行,结果为 false 。
我重新建了个 html 页面,把你的代码粘贴进去运行还是 false,这种没修改都是 false 的要怎么判断?
rekulas
2021-05-09 10:52:54 +08:00
@musi
应该是这句 != 'function XMLHttpRequest() { [native code] }'
这个在不同环境下有一点差异所以需要尝试下多种环境,我使用的 chrome90,其他版本或 firefox 的话需要针对性判断下
你可以先 console.log(XMLHttpRequest.valueOf()) 拿到真实值替换进去再测试
musi
2021-05-09 10:56:06 +08:00
@rekulas 可能我表达有问题,我说的是 51 楼的代码
rekulas
2021-05-09 11:15:31 +08:00
@musi 测试好像是不行了,奇怪昨天我测试还可以,我待会再试试
不过不用纠结这个,因为这个最终还是有漏洞的,你可以看看我最新的代码如何绕过
Huelse
2021-05-09 11:27:12 +08:00
幸好楼主解释了,不然又是一个 x-y 问题了
musi
2021-05-09 13:25:27 +08:00
@rekulas #63 也不是很难
```
let tem = XMLHttpRequest;
function test() {
console.log(111)
return tem
}
XMLHttpRequest = test
isNative(XMLHttpRequest) // false
test.toString = () => "function XMLHttpRequest() { [native code] }"
isNative(XMLHttpRequest) // true
```
gzzhanghao
2021-05-09 16:01:26 +08:00
function guard(value) {
function toString() {
try {
null.a
} catch (error) {
// TODO 判断 error.stack,在 chrome 下 send 和 Object.toString 应该是相邻的
// 注意:不能用 prototype,只能访问 str.length 和 str[index],逐个字符判断
console.log(error.stack)
return value
}
}
return { toString }
}

function send(method, url, body = undefined) {
const xhr = new XMLHttpRequest()
xhr.open(guard(method), guard(url))
xhr.send(guard(body))
}

send('POST', '/', JSON.stringify({ foo: 'bar' }))

/**
* 测试劫持
*/

XHR = XMLHttpRequest
XMLHttpRequest = class {
constructor() {
this.xhr = new XHR()
}
open(method, url) {
return this.xhr.open(method, url)
}
send(body) {
return this.xhr.send(body)
}
}

send('POST', '/', JSON.stringify({ foo: 'bar' }))
gzzhanghao
2021-05-09 16:17:40 +08:00
https://codepen.io/gzzhanghao/full/PopqWZr
v2 回复居然没有缩进…
gzzhanghao
2021-05-09 16:37:15 +08:00
我错了,不用判断 stack 那么麻烦,直接 fn.caller 就能秒解

```js
function guard(value) {
function toString() {
if (toString.caller === send) {
return value
}
}
return { toString }
}

function send(method, url, body = undefined) {
const xhr = new XMLHttpRequest()
xhr.open(guard(method), guard(url), false)
xhr.send(guard(body))
console.log(xhr.status, xhr.responseText)
}

send('GET', '/')
```
rekulas
2021-05-09 21:47:24 +08:00
@musi 确实又绕过了。。又升级了下,再看看呢

```
```
function isNative(api) {
if (XMLHttpRequest.hasOwnProperty('toString')) return false;
if (typeof XMLHttpRequest != 'function') return false;
if (typeof XMLHttpRequest.valueOf != 'function' ||
XMLHttpRequest.valueOf().toString() != 'function XMLHttpRequest() { [native code] }') {
return false;
}
return /native code/.test(api.toString()) && typeof api !== 'undefined'
}

console.error(isNative(XMLHttpRequest))
```
```
BoringTu
2021-05-10 10:07:27 +08:00
@luofeii #41 但是你怎么做对比呢?
如果不考虑是否是 electron 环境,只是浏览器环境的话,你可以参考我提供的思路,这是最简单也最有效的解决方案
要的就是同一个对象的引用,这样才能判断出是否是同一个内存地址

@binux 为啥做不到呢?在.html 文件里直接写死就好啊,你是考虑会有动态插入 script 标签么?这不需要考虑啊,HTML 在浏览器内核上的渲染逻辑没有那么玄幻,都是自上而下的,就算有动态插入也都是执行到了具体脚本才会有的动作,但早在这一步之前,我想要执行的那句脚本就已经执行完毕了

@rekulas 嗯,我不推荐 iframe 的这种做法哇,虽然按我最早发的那个思路,一样是可以避免你提的这个可能的,我拿到内置 document.createElement 的原生函数引用不就好了嘛。而且你的思路最后的那个判断本来就不是应该的,这个判断木有意义:“XMLHttpRequest === iframe.contentWindow.XMLHttpRequest”,如果不执行你的第一句那个重写 createElement,前面这个判断是永远是 false 的,因为并不是同一个环境,所以也就不是同一个对象

@aaronlam
@musi
以及楼上某些木有点名到的童鞋,你们都没注意听讲啊。。
我给各位总结然后回答一下楼主这个问题(两种情况):

1. 如果只是判断前端环境中是否有脚本重写了 XMLHttpRequest,那最简单也最靠谱的就是我提供的那个方案:
“在 HTML head 标签里所有 script 标签的最前面加上一个 script,里面:
window.originXMLHttpRequest = window.XMLHttpRequest;
然后在你想判断的时候:
originXMLHttpRequest === XMLHttpRequest”
(顺便聊一下 iframe 的这个方案,用来判断的话是没戏的,上下文环境不同,就算没被重写,也没法判断,因为不可能是同一个对象。iframe 的方案,你只能是 iframe 环境里的 XMLHttpRequest 拿来直接当做原生内置函数来用

2. 如果是说 electron 直接修改了浏览器内核里的 XMLHttpRequest,那就无解了,人家都不是在前端环境修改的,你怎么判断?你就算 toString 了,一样拿到的是这个:'function XMLHttpRequest() { [native code] }'。这种情况你想要知道是否被重写了,只能肉眼去看 electron 的源码,没有其他方式了
binux
2021-05-10 10:10:48 +08:00
@BoringTu 如果我控制了浏览器呢?甚至不需要是浏览器,一个插件,mitm 修改 HTML 都行。
BoringTu
2021-05-10 10:26:29 +08:00
@binux 你怎样控制用户的浏览器呢?你说插件,你只能在自己电脑上安装插件,你怎样让用户也安装?如果你随意的就能控制他人浏览器,那浏览器这个行业也不用干了。。
你只能在你电脑的浏览器上装你想装的插件,然后你在自己浏览器上通过插件篡改了原生函数,这都可以啊,但能说明啥问题。。
这个其实没啥好杠的,而且都不需要去查什么资料去验证
这就好像之前我碰到有问这么个事儿的,说我通过自己浏览器开发者工具拿到了我登录后的 token,然后发给别人,不就把我的登录信息暴露出去了么。。我。。当时是真不知道该回啥。。
SakuraKuma
2021-05-10 11:07:37 +08:00
囧, 楼主都说是 electron, js 怎么弄都么得用, 还是上面说的走 c++写 dll 用 ffi 吧.
binux
2021-05-10 11:29:44 +08:00
@BoringTu 请认真阅读 #18
gzzhanghao
2021-05-10 11:32:06 +08:00
@BoringTu 楼主好像说过不能控制 script 标签的位置,另外这里应该不用讨论改内核的场景,那是无解的
musi
2021-05-10 12:06:45 +08:00
@BoringTu #72 你没仔细看我说的,我说的就是直接用 iframe 的 xhr 去请求,就不判断了。至于修改内核,能修改内核了你只靠 js 是无解的。
还有,你的方案也不是最简单的,我要是多页面那我每个页面都要修改这侵入性也太大了,这也能叫最简单的?
BoringTu
2021-05-10 14:16:07 +08:00
@musi 就算再多页面,你肯定也是按模板来的,难道你这多页面要每个页面都单独完整写一份 html ?
而且你谈侵入性,我的方案只是暴露原对象引用出来而已,并没有做哪怕一丁点的修改,这叫有侵入性?
至于你前半段说的,我都已经说过一遍了。。


@gzzhanghao 为啥不能控制?详见 #72
gzzhanghao
2021-05-10 18:19:54 +08:00
@BoringTu 见#27,另外我之前也遇到过类似的问题,作为第三方库提供出去,这种情况是控制不了的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/775677

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX