一、ROP思想
在栈缓冲区溢出的基础上,利用程序中已有的小片段( gadgets )来改变某些寄存器或者变量的值,从而控制程序的执行流程,所谓gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程
之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件
- 程序存在溢出,并且可以控制返回地址。
- 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
如果 gadgets 每次的地址是不固定的,那我们就需要想办法动态获取对应的地址了
二、Linux-Pwn的一些操作
1、checksec 工具检查编译出的文件
1 | kitty@ubuntu:~/桌面/pwn$ checksec stack-example |
1.Canary(栈保护)
这个选项表示栈保护功能有没有开启。
栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
2.NX/DEP(堆栈不可执行)
NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
3.PIE/ASLR(地址随机化)
4.Fortify
这个保护机制查了很久都没有个很好的汉语形容,根据我的理解它其实和栈保护都是gcc的新的为了增强保护的一种机制,防止缓冲区溢出攻击。由于并不是太常见,也没有太多的了解。
5.RelRO
设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。
2、readelf/objdump
readelf -h ret2text
查看头信息readelf -S ret2text
显示节区表内的所有信息,包括每个节的属性readelf -S ret2text
显示节区表内的所有信息,包括每个节的属性readelf -t ret2text
用来显示所有节的详细信息readelf -l ret2text
显示程序头表信息,包扩有几个段,每个段的属性readelf -r ret2libc1
显示所有重定位入口的信息objdump -s ret2text
显示指定section的完整内容objdump -h ret2text
显示目标文件各个段的头部摘要信息:objdump -d ret2text
显示汇编:
3、GDB
断点pwndbg> b *0x80486AE
运行pwndbg> r
4、查看段读写权限
1 | gdb> b main |
5、ROPgadget
寻找gadget
1 | kitty@ubuntu:~/桌面$ ROPgadget --binary rop --only 'pop|ret'|grep 'eax' |
查字符串
1 | kitty@ubuntu:~/桌面$ ROPgadget --binary rop --string '/bin/sh' |
寻找int 0x80 的地址
1 | kitty@ubuntu:~/桌面$ ROPgadget --binary rop --only 'int' |
三、IDA的操作
1、查看栈结构
双击变量即可
2、查看段
ctrl+S
四、一些经典的题目
1、stack-example
最典型的栈溢出利用是覆盖程序的返回地址为攻击者所控制的地址,当然需要确保这个地址所在的段具有可执行权限
1 | ##coding=utf8 |
2、ret2text
ret2text 即控制程序执行程序本身已有的的代码(.text)
1 | .text:080486A7 lea eax, [esp+80h+s] //其中s为-0x64 |
所以偏移地址计算方式
1 | ##!/usr/bin/env python |
3、ret2shellcode
ret2shellcode,即控制程序执行 shellcode代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码。
1 | #!/usr/bin/env python |
bss可执行后,把shellcode放到bss段
4、利用gadgets来getshell
只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell
1 | execve("/bin/sh",NULL,NULL) |
其中,该程序是 32 位,所以我们需要使得
1 | 系统调用号,即 eax 应该为 0xb |
而我们如何控制这些寄存器的值 呢?这里就需要使用 gadgets。比如说,现在栈顶是 10,那么如果此时执行了pop eax,那么现在 eax 的值就为 10。
ps:
1 | ret 不加参数 相当于 用栈中的数据,修改IP的内容 pop IP |
1 | #!/usr/bin/env python |
pwntools打断点
1 | sh = process('./stack-example') |