Post

[Linux] Stack Alignment

문제를 풀다가 system("/bin/sh")을 잘 호출했는데 익스가 안되는 경우가 있다.

1
<do_system+115>    movaps xmmword ptr [rsp], xmm1

여기서 익스가 안되어서 원인을 찾다보니 Stack Alignment 문제라는 것을 알게 되었다. 이미 [Research] x64 stack alignment 여기서 언급한거 처럼 Ubuntu 18.04부터 do_system()movaps인스트럭션이 추가되어서 Stack Alignment를 지켜주지않으면 코드가 실행되지 않는다.

Stack Alignment

Stack Alignmentrsp의 메모리 주소가 16배수로 유지된 상태이고, rsp의 위치가 정해지는 규칙(프로그램의 흐름이 함수의 Entry로 옮겨지는 시점에선 rsp+8이 항상 16의 배수 등)을 지켜야 한다.

간단히 정리하면 다음과 같다.

  • call 실행 직전 rsp는 16의 배수 (Stack Alignment O)
  • 함수의 Entry Point에선 rsp+8이 16의 배수 (Stack Alignment X)
  • 함수의 프롤로그 실행 후 rsp는 16의 배수 (Stack Alignment O)
  • rbp는 항상 16의 배수 (Stack Alignment O)

여기서 생각해야할 것은 ‘왜 함수 Entry Point에서는 rsp+8이 16의 배수가 되어야 하는지’이다. 이를 위해서는 call 명령어를 알아야 한다.

call 명령어는 두 가지 동작을 수행한다. 먼저, 돌아올 주소를 저장하기 위해 현재 명령어의 다음 주소를 스택에 push한다. 그리고 jmp 명령어를 통해 해당 함수의 주소로 이동하여 함수를 호출한다. 이렇게 되면 rsp는 8만큼 감소하기 때문에 함수의 Entry Point에선 rsp+8이 16의 배수가 되어야 한다.

그 다음, 함수 프롤로그로 인해 rsp는 8이 증가하고 rsp는 16의 배수가 된다. 또한 함수 프롤로그에서 rbprsp의 값을 들고오기 때문에 rbp는 항상 16의 배수가 된다.

마지막으로 에필로그에서는 leaveret동작을 하게 된다. leave로 인해 rsp는 8이 증가하고, ret으로 인해 다시 8이 증가한다. 하지만 보통 익스플로잇 코드를 작성하게 되면 ret을 덮는 경우가 대부분이고, ret으로 변조된 함수를 호출하기 때문에 Stack Alignment가 깨진다. 그래서 do_system()에서 movaps명령어를 만나면 segmentation fault가 나타나고 익스플로잇을 하지 못한다.

Stack Alignment의 개념과 Stack Alignment가 깨지는 경우에 대해 설명했다. 이제 Stack Alignment를 맞춰주기 위해서는 익스플로잇 코드에 ret 가젯을 하나 더 추가해서 rsp를 조정해주면 된다.

1
2
3
4
5
6
...
payload = ...
payload += b"B" * 0x8
payload += p64(ret)         # Stack Alignment
payload += p64(get_flag)
...

Etc..

Stack Alignment가 필요한 주요 이유는 메모리의 Access Cycle을 최소한으로 줄여 Performance를 높이기 위함이다.

Stack Alignment가 필요한 명령어는 주로 SSE(Single Instruction, Multiple Data)AVX(Advanced Vector Extensions) 명령어이다.

  • SSE 명령어: movaps, movapd, movdqa
  • AVX 명령어: vmovaps, vmovapd, vmovdqa

Ref

[1] [Research] x64 stack alignment

[2] Stack Alignment

[3] Stack Alignmnet in x86-64

[4] Intel Documentation Library