rop_x86
CTF中的ROP
顾名思义ROP,就是面向返回语句的编程方法,它借用libc代码段里面的多个retq前的一段指令拼凑成一段有效的逻辑,从而达到攻击的目标。
源码
|
|
运行命令生成gcc -m32 -no-pie -fno-stack-protector -z execstack -o pwn2 pwn2.c
漏洞原理,利用rop结合ret2blic技术
1、找到system_addr,gets_addr,sh_addr,这些都可以用
system_addr=elf.symbols(‘system’)
system_plt = elf.symbols[‘system’]
get_plt = elf.symbols[‘gets’]
sh_addr= elf.bss()
2、找到gadgets,也就是利用rop
先得到gadgets,这里大神都用ROPGadget,不过我还不会用,我是用objdump,执行以下命令objdump -d ./pwn2 | egrep 'ret|pop'
结果如下
现在我用最后一个gadgets,pop_ebx=0x8048586
构造payload:
payload=’a’*112+p32(get_plt)+p32(pop_ebx)+p32(sh_addr)+p32(system_plt)+p32(1)+p32(sh_addr)
p32(get_plt):函数gets的开始地址 ,后面接返回地址[p32(pop_ebx)],再后面是要写入字符的地址[p32(sh_addr)]
p32(pop_ebx):这个gadgets,将栈中他后面一个8字节的数据弹出[p32(sh_addr)],然后运行返回地址[p32(system_plt)]
p32(sh_addr):这是bss的地址(可读可写可执行),为了将’\bin\sh’写入这里,也是函数gets[p32(get_plt)]的参数
p32(system_plt):这是p32(pop_ebx)函数的返回地址,这里改为我们要执行的system的地址
p32(1):system的返回地址,因为用不到,这里我随便填的。
p32(sh_addr):system的第一个参数,要执行字符串的地址,我们要拿到shell,就填’/bin/sh’的地址
exp
|
|
rop_x64
题目-全球某工商的ctf网站上的ctf题(还有源码福利)
题目
链接:http://pan.baidu.com/s/1dF1Z3e5 密码:jx30
分析
查看文件类型file pwn1
发现是64位的,吓我一跳,了解一下x64的特点
x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。
如write(rdi,rsi,rdx)
先明确目标
1、用于任意地址读
2、用于任意地址写
3、获取shell
可以看到这个程序仅仅只有一个buffer overflow,也没有任何的辅助函数可以使用,所以我们要先想办法泄露内存信息,找到system()的值,然后再传递“/bin/sh”到.bss段, 最后调用system(“/bin/sh”)。因为原程序使用了write()和read()函数,我们可以通过write()去输出write.got的地址,从而计算出libc.so在内存中的地址。但问题在于write()的参数应该如何传递,因为x64下前6个参数不是保存在栈中,而是通过寄存器传值。我们使用ROPgadget并没有找到类似于pop rdi, ret,pop rsi, ret这样的gadgets。那应该怎么办呢?其实在x64下有一些万能的gadgets可以利用。比如说我们用objdump -d ./pwn2观察一下__libc_csu_init()这个函数。一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作。
objdump -d pwn1
如下:
我们可以看到利用0x4005fa处的代码我们可以控制rbx,rbp,r12,r13,r14和r15的值,随后利用0x4005e0处的代码我们将r15的值赋值给rdx, r14的值赋值给rsi,r13的值赋值给edi,随后就会调用call qword ptr [r12+rbx8]。这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)。执行完call qword ptr [r12+rbx8]之后,程序会对rbx+=1,然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。大概思路就是这样,我们下来构造ROP链。
那就先来第一步吧,任意地址读,这里我们需要gadget
注意我们的gadget是call qword ptr [r12+rbx*8],所以我们应该使用write.got的地址而不是write.plt的地址。payload1任意地址读
第二步吧,任意地址写
第三步吧,获取shell
exp
|
|
这里记录一下我走过的坑,
1、首先是第一张4005e6 mov %r15d,%edi,如果bin/sh所在的地址大于4字节,rdi!=edi(这是第一个参数)那么就要过这里,先把bin/sh存放在一个毕竟小的地址,bss段
,再将bss地址 传入edi,便可以绕过
2、在本地运行时libc.so.6使用自己的,而连接上目标机器使用他给的libc.so.6
DynELF泄露函数地址
源码
|
|
运行命令生成gcc -m32 -no-pie -fno-stack-protector -z execstack -o pwn3 pwn3.c
步骤
没有system函数和libc.so
1、找到system函数
2、将\bin\sh写入bss段
3、利用gadgets构造system(‘\bin\sh’),得到shell
漏洞原理,利用pwntools中的DynELF模块来寻找system_addr
这里我们采用pwntools提供的DynELF模块来进行内存搜索。首先我们需要实现一个leak(address)函数,通过这个函数可以获取到某个地址上最少1 byte的数据。
随后将这个函数作为参数再调用d = DynELF(leak, elf=ELF(‘./pwn3’))就可以对DynELF模块进行初始化了。然后可以通过调用system_addr = d.lookup(‘system’, ‘libc’)来得到libc.so中system()在内存中的地址。这里的leak函数是这样的:
write(fb,addr,len)对应
write(p32(1),p32(address),p32(4))
p32(write_plt):这是原返回地址,改为write_plt的地址,p32(main)是write_plt的返回地址 ,p32(1)是write_plt的参数1代表读,0代表写,p32(address)是write_plt要读的地址,p32(4)是write_plt要读的字节数
这样就会循环读取函数地址 ,读取地址
结果
这里我们已经得到system_addr了
构造payload3,得到shell
找gadgets利用objdump -d ./pwn3|egrep "pop|ret"
因为我们需要pop pop pop ret,这里我选择pppr=0x80484ad
5、整个攻击过程如下:首先通过DynELF获取到system()的地址后,我们又通过read将“/bin/sh”写入到.bss段上,最后再调用system(.bss),执行“/bin/sh”。最终的exp如下:
|
|
记录一下自己走过的坑,不知道找gadgets利用的时候pppr=0x80484ad,程序就会莫名的中断 ,
调试发现,当跳到0x80484ad这个地址时,程序就退出了。换个pppr=0x8048519,运行成功。
可能是 那个gadgets有问题,不知道了,不过可以多换几个试试