x86 call convention & how to ROP in x86

기존에 x86이 스택을 통해 인자를 받는 것을 당연히 알고 있을 것이다.

이번에는 어떻게 받는지 좀 더 자세하게 알아보도록 하자 그리고 나아가 ROP 체인을 만드는 관점에서 알아보자

C
   0x080485f6 <+29>:    push   $0x400
   0x080485fb <+34>:    lea    -0x44(%ebp),%eax
   0x080485fe <+37>:    push   %eax
   0x080485ff <+38>:    push   $0x0
   0x08048601 <+40>:    call   0x80483f0 <read@plt>

다음은 일반적인 read함수를 부르는 과정이다. 인자가 3개 답게 push를 3번함을 알 수 있다. 그럼 이제 함수 내부에서 볼건데 우리가 함수의 스택을 계산할때 생각해야 하는 건 $rsp와 $rbp의 움직임이다.

지금도 사실 call 함수가 불리는 순간 $rsp 는 0x4만큼 작아진다. RA가 들어가기 때문이다. 그리고 추가적으로 함수의 프롤로그에서 당연히 $rbp 가 push 된다. 따라서 $rsp - 0x8이 된다.

즉, 다음과 같이 스택이 구조 된다는 것이다.

그리고 ROP를 작성할때는 내가 어디를 기준으로 ROP를 작성하는지 계속 생각해야 한다. 예를 들어 read@plt 함수로 ROP를 짠다면

이상태를 가정하고 짜야한다. 왜냐하면

C
Dump of assembler code for function read@plt:
   0x080483f0 <+0>:     jmp    *0x804a00c
   0x080483f6 <+6>:     push   $0x0
   0x080483fb <+11>:    jmp    0x80483e0

read@plt는 call만 된 상태이지 아직 진짜 Libc 함수로 가서 $rbp 가 push가 도기 전이기 때문이다. 따라서 payload를 짤때

Python
payload += p32(read_plt) 
payload += p32("다음 ROP체인")       
payload += p32(0)
payload += p32(read_got + 0x100)
payload += p32(8)

다음처럼 RA 위치에 다음 ROP 체인 주소를 넣는 것이 일반적이다.

근데.. 생각을 해보면 다음 ROP 체인 주소를 넣는다고 해도 아래 스택 3개를 치울 수 있는 방법이 필요하다. ( 다음 ROP 체인 함수에 필요가 없는 인자니깐 )

이럴때 주로 쓰는 방법중 2가지가 있는데

  1. main 함수문 그대로 사용하기
  2. pop을 원하는 갯수 만큼 해주는 가젯 사용

다음과 같다.

1번은

C
   0x080485f6 <+29>:    push   $0x400
   0x080485fb <+34>:    lea    -0x44(%ebp),%eax
   0x080485fe <+37>:    push   %eax
   0x080485ff <+38>:    push   $0x0
   0x08048601 <+40>:    call   0x80483f0 <read@plt>
   0x08048606 <+45>:    add    $0xc,%esp

다음과 같이 main함수처럼 read 함수를 원래 프로그램의 목적으로 부르는 코드에서는 함수가 끝나고

0x08048606 <+45>: add $0xc,%esp

처럼 스택을 정리를 해준다. 따라서 스택을 관리하기 쉽다.

(아니면 main함수를 아예 처음 부터 실행해도 됨 )

2번은

다음 페이지를 참고하자