본문 바로가기

Let's Study/Basic System Hacking Technique

6. Return Oriented Programming (ROP)


system_hacking7.pdf





sua_system7.m4v



final_flag


6. python programming 관련 동영상입니다. 설명은 생략하도록 할게요. 필요하신 분은 참고하세요!


flag_1


flag_2


flag_3


python


python.c


python 바이너리 사용법은 ./python [port] 입니다.




7. ROP



6은 건너뛰고 왜 7이냐면, 중간에 python basic programming 이 있었지만 설명할 게 별로 없으므로 넘어가기로 했습니다. 


이번 7번째 강의시간은 Return Oriented Programming을 배워보도록 합니다. 이 기법은 리얼월드에서도 자주 쓰이는 기법으로 , 아직까지 ROP를 막는 방법이 연구되지 않았습니다. 


 ROP(Return Oriented Programming)는 현재 수행중인 프로그램 코드 안에 존재하는 서브루틴이 리턴 명령어에 닿기 전에 선별된 기계 명령어 또는 기계 명령어 덩어리를 간접적으로 실행시키기 위해 콜 스택을 제어하는 기술입니다. 

 실행되는 모든 명령어들이 원래 프로그램 안에 존재하는 실행 가능한 메모리 영역에서 추출한 것들이기 때문에, 이러한 기술은 사용자 제어 메모리 공간에서 명령어 수행을 방지하는 기술(DEP, ASLR)들을 우회하는 코드 인젝션과 같은 기술들을 사용하지 않아도 우회가 가능합니다.


1. Basic Knowledge



 이러한 ROP를 공부하기 위해서는 기본적인 지식이 필요합니다. 저희들이 지금까지 공부해왔던 기술들과, 이번에 새로 배울 gadget입니다. DEP, ASLR은 전에도 말씀드렸듯이 스택에 실행권한을 주지 않는  기법과 공유라이브러리의 주소를 랜덤하게 바꾸는 기법입니다.  1부터 배웠던 지식들을 차근차근 살펴보면서 ROP 진행하겠습니다.




 PLT, GOT 개념 정리할 때 보여드렸던 그림입니다. 함수가 처음 불러지기 전에 plt에서 got로 넘어간 후 _dl_runtime_resolve, _dl_fixup, _dl_lookup_symbol_x를 차례로 지나가면서 실제 함수의 이름이 들어있는 함수이름주소와 SYMTAB, libc 주소를 얻어 실제 함수의 주소를 구하는 과정입니다.




 위의 그림에는 10번이 없었습니다. 9번까지 진행해서 real libc address를 구한뒤 10번, got에 실제 함수의 주소를 넣습니다. 함수 주소 구하는 과정이 다 끝나고 _dl_runtime_resolve가 정상적으로 return 되면 실제 함수 주소로 return 되어 함수가 실행됩니다. 임의 함수가(공유라이브러리의 함수가) 처음 불러질 때의 과정은 이렇습니다. 이렇게 한번 불러지고 나면 ,



 

 복잡하게 실제 함수의 주소를 구할 필요없이 got에 실제 함수의 주소가 들어있기 때문에 got에서 바로 실제함수로 넘어가게 됩니다. 여기까지가 plt, got가 어떻게 돌아가는지 원리를 알아보는 과정이었습니다. 이 과정은 RTDL (Return to Dynamic Linker)를 할 때에 필요한 지식이므로 공부하고 싶으신 분은 이것 관련된 자료를 찾아보시기 바랍니다. 보통은 그냥 plt에서 got로 넘어가고 _dl_runtime_resolve , _dl_fixup, _dl_lookup_symbol_x 과정을 거쳐 실제 주소가 구해지고 그 주소가 got에 들어간다 정도만 알아두시면 됩니다. 

 아무튼 이런 plt, got를 알게 되면 got overwrite가 어떻게 되는지 아실겁니다.




 실제 주소가 들어있는 got 주소에 있는 값을 해커가 원하는 arbitrary function 즉 해커가 원하는 임의의 함수주소로 바꾼다면 특정함수가 실행되어야 하는데 해커가 원하는 주소로 실행되버리는 겁니다. 예를 들어 puts_got 의 값을 system주소로 바꿨을 때 puts("/bin/sh")가 실행되지 않고 system("/bin/sh")이 실행되는 것처럼요.

여기까지 plt, got, got_overwrite를 간단하게 알아보았습니다.




  RTL Chaining 기법은 RTL 설명드릴 때 잠깐 언급했는데 buffer + sfp + return address 를 입력하면 저희가 원하는 주소로 리턴할 수 있었는데 여기서 buffer + sfp + return address + dummy + parameter 를 입력하면 원하는 주소로 넘어가서 원하는 parameter를 실행할 수 있었습니다.


 이 payload에서 dummy는 의미없는 값이 아닌, 다음 리턴 어드레스를 가리키고 있다고 설명드렸는데, RTL Chaining 기법은 이 dummy를 이용해 연속적으로 함수를 호출하는 기법입니다. 취약한 바이너리 내부에서 dummy가 위치한 곳에 부를 함수의 인자만큼 pop이 있는, pop pop ret 가젯을 찾을 수 있다면 원하는 함수들을 연속적으로 호출이 가능합니다. 가령, system(), printf()를 호출하고 싶을 때, 

payload = buffer + sfp + system_address + pop ret + "/bin/ls" 문자열이 위치한 주소 + printf_address + dummy + "임의 문자열"이 있는 주소

이렇게 말이지요. system() 함수는 인자값이 한 개 들어있기 때문에 pop ret을 이용하면 스택에서 다음 return address (pop ret)를 실행하고 함수 인자 한 개를 pop 한개가 정리해 준 후, 그 뒤에 선언한 printf_address로 ret (return) 하게 됩니다.




 이 쯤에서 Gadget이 뭔지 궁금하실건데, 이 Gadget이란 개념은 원래 코드 조각을 지칭할 때 쓰였지만 요새는 ret로 끝나는 연속된 명령어들을 의미하게 되었습니다. 

pop pop ret은 다음 return address를 가리키기 위해 사용했던 인자들을 pop하고 (사용했던 인자들의 갯수만큼 pop해야 함수인자들이 정리됩니다.) esp를 다음 return address가 있는 스택으로 맞춰 return하기 위해 Gadget을 사용합니다.

 혹시 누가 "pop pop ret"이 뭐하는거에요? 라고 물어보면 간단하게 "이건 그냥 함수 인자들을 정리해주기 위해 사용하는 거에요." 라고 대답해주시면 됩니다.




 프로시저를 사용하는 대신 Gadget을 이용하고, 모든 Gadget은 ret으로 끝나야 합니다. 다음 주소를 호출하기 위함입니다. 그리고 Gadget은 의도된 특정 행동을 수행합니다. 가령 Gadget을 구성할 때 pop pop ret이 아닌 mov esp, ebp, ret 이나 jmp eax같은 명령어들을 사용할 수도 있기 때문에 의도된 특정 행동을 수행한다고 표현했습니다. 이러한 특성으로 인해 공격자는 여러 개의 Gadget을 조합해 공격의 정교함을 더할 수 있습니다.


2. Return Oriented Programming



 저는 이러한 Return Oriented Programming 실행하는 과정을 퍼즐맞추기 혹은 케익만들기에 비유합니다. 공격할 Gadget들을 취약한 바이너리 내에서 찾아 공격코드를 구성하는 과정은 마치, 미리 케익재료들이 방안에 있는데 이것들을 찾아 자신이 원하는 대로 케익을 만드는 과정이라 생각합니다. 혹은 수 백, 수 천 piece의 퍼즐을 맞추기 위해 퍼즐을 모아 하나하나씩 맞추는 과정이라고도 하지요. 기본적인 설명이 끝났으니 이제 ROP에 대한 설명을 시작하도록 할게요.



 

 ROP의 기본적인 개념을 배울 때에 가장 간단하게 실습할 수 있는 환경은 소켓프로그래밍의 send(), recv() 함수를 이용해 취약점을 일으키는 겁니다. 

예를 들어서 서버의 buf가 128바이트만큼 할당되어 있고 recv로 200만큼 받을 수 있을 때, recv(fd, buf, 200, 0) buf보다 더 높은 값을 받는다면 buffer overflow가 일어날 수 있습니다. recv() 함수가 불러질 때에 recv_plt로 넘어가게 되는데 스택상태를 확인해보면 맨 위에 다음에 실행될 return address, parameter1, parameter2, parameter3, parameter4가 들어있습니다. 




 recv()를 이용해 buffer overflow를 일으키고 send_plt + dummy(ABCD) + hacker_fd + recv_got + 4 + 0을 스택에 넣게 되면 send(hacker_fd, recv_got, 4, 0)이라는 함수가 실행되지요. 즉 저희들이 원하는 인자값을 넣어서 실행시킬 수 있습니다.



 이 과정은 이렇게 설명드릴 수 있습니다. 간단하게 help라는 함수에 recv()가 있는데 recv()에서는 받는 상태가 됩니다. vuln이라는 배열은 100바이트만큼만 받을 수 있는데 recv()는 200만큼 받을 수 있으므로 buffer overflow가 일어납니다.





 ROP 기법은 Stage 1과 Stage 0이 있습니다. Stage 1에서는 실행하고 싶은, 원하는 함수의 주소를 계산하는 과정이고 Stage 0은 계산된 주소를 직접 사용해 공격하는 과정입니다. ROP를 간단하게 설명드리면 직접 사용하고 싶은 주소를 계산해 RTL Chaining으로 연속적인 함수를 호출하고, 해커가 원하는 결과에 도달하는 과정입니다.




 자, 여기서 send() 함수를 이용했을 때에 recv_got를 이용했는데 굳이 recv_got를 써야할 이유는 없습니다. 우선 recv_got보다 왜 got 주소를 send에 넣었는지 설명드리겠습니다.

 got 주소에는 실제 함수의 주소가 들어있다고 말씀드렸습니다. 실제 주소가 들어있다는 건, send(fd, got_address, 4, 0)을 썼을 때 특정 fd에 got_address에 들어있는 값을 4바이트만큼 출력하겠다는 의미입니다. 즉 got_address에 있는 실제 함수의 주소 4바이트가 hacker의 터미널에 출력된다는 뜻입니다. 해커는 출력된 4바이트를 받아와서 system offset을 계산할 수 있습니다.



 

 여기서 의미하는 offset이란 특정 함수들의 거리 차이를 뜻합니다. 예를 들어 libc.so 공유 라이브러리에 recv() 함수가 150 Line에 선언되어 있고 system() 함수가 300 Line에 선언되어있다고 가정했을 때 system - recv = 150 이 되겠지요? 300 - 150 이니까요. 그럼 계산된 150을 offset이라 하면,  recv + offset = 150 + 150 = 300. 시스템의 주소를 가리키게 됩니다. 이런 원리로 시스템 주소를 구할 수 있습니다. 

 offset = recv - system 일 때, system = recv - offset 인데 간단한 사칙연산 생각하시면 편합니다. 




 Stage 1에서 system 주소를 구했으니 Stage 0에서는 이를 이용해 공격해보도록 합니다. 소켓을 끊고 다시 연결해서 서버의 recv()로 넘어가서 overflow를 일으키고 RTL Chain을 이용해 함수를 연속호출합니다. 자세한 설명은 그림을 참조하도록 해요.




 bss section은 초기화되지 않은 전역변수들을 위한 곳으로 보통 이 영역은 모든 변수들이 0으로 초기화되어 있고 이 곳은 주소값이 변하지 않습니다. 해서, 해커들은 이 영역을 임시스택이라고도 칭합니다. 여기에 "/bin/sh" 문자열을 넣어서 이 주소를 가리키면 system("/bin/sh")를 실행할 수 있습니다.



Return Oriented Programming을 이용한 실습은 첨부된 pdf를 확인해보세요. 뒤쪽에 실습하는 과정을 자세하게 적어놓았습니다.

실습에 사용된 바이너리와 소스코드도 첨부하도록 하겠습니다.


글이 너무 길어질 것 같아 여기서 끊도록 할게요. 설명이 더 필요한 부분 질문드리면 답변해드리거나 더 내용추가하도록 하겠습니다.

'Let's Study > Basic System Hacking Technique' 카테고리의 다른 글

8. Core Dump Analysis ( gdb )  (4) 2015.11.17
7. Use After Free  (11) 2015.11.17
5. Format String Attack (FSB)  (19) 2015.10.27
4. Return to Library (RTL)  (25) 2015.10.07
3. 핸드레이 & 기본 어셈블리 명령어  (16) 2015.09.29