2017. 11. 17. 13:20ㆍSystemHacking/LOB(BOF원정대)
RTL ( Return To Library )
스택에 실행권한을 없애 스택에 쉘 코드를 올리는 것을 막는 오버플로우 대응책을 우회할 수 있는 기법
간단히 말하면 프로세스의 RET 주소를 다른 함수의 주소로 변조시키면 해당 함수로 Jmp되어 해당 함수가 실행 된다
오메가 프로젝트에서 발전된 기법이다 예제를 통해서 알아보자
간단한 C프로그램을 하나 작성하습니다 ( #vi omega.c )
간단한 printf 함수를 호출하는 func1, func2, func3, func4 함수들을 선언했고, main함수에는 버퍼오버플로우 취약점이 존재한다
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 | #include <stdio.h> void func1() { printf("Call Func1\n"); } void func2() { printf("Call Func2\n"); } void func3() { printf("Call Func3\n"); } void func4() { printf("Call Func4\n"); } int main( int argc, char* argv[] ) { char buffer[40]; if ( argc >= 2 ){ strcpy( buffer, argv[1] ); } printf("Call Main\n"); return 0; } | cs |
< 프로그램 어셈블리 코드 >
0x8048450 <main>: push %ebp 0x8048451 <main+1>: mov %ebp,%esp 0x8048453 <main+3>: sub %esp,40 0x8048456 <main+6>: cmp DWORD PTR [%ebp+8],1 0x804845a <main+10>: jle 0x8048471 <main+33> 0x804845c <main+12>: mov %eax,DWORD PTR [%ebp+12] 0x804845f <main+15>: add %eax,4 0x8048462 <main+18>: mov %edx,DWORD PTR [%eax] 0x8048464 <main+20>: push %edx 0x8048465 <main+21>: lea %eax,[%ebp-40] 0x8048468 <main+24>: push %eax 0x8048469 <main+25>: call 0x8048340 <strcpy> 0x804846e <main+30>: add %esp,8 0x8048471 <main+33>: push 0x8048510 0x8048476 <main+38>: call 0x8048330 <printf> 0x804847b <main+43>: add %esp,4 0x804847e <main+46>: xor %eax,%eax 0x8048480 <main+48>: jmp 0x8048482 <main+50> 0x8048482 <main+50>: leave 0x8048483 <main+51>: ret |
[ 함수의 에필로그 부분 ]
0x8048482 <main+50>: leave : mov esp, ebp + pop ebp
* 함수의 에필로그 부분 leave , ret 을 통해서 스택에 저장해둔 복귀주소와 다음 실행명령어 주소를 이용해 프로세스가 진행된다
* RTL은 ret 명령에서 사용되는 eip 주소의 값을 변조시켜서 프로세스의 실행 과정을 바꾼다
* eip주소를 변조시켜서 main함수의 종료 후 다른 함수를 호출되도록 해보자
[ 예제 1 ]
원리는 간단하다. 우선 해당 프로그램의 스택구조를 살펴보자
[ 스택메모리 구조 ]
buffer ( 40byte ) | A * 40 |
save ebp ( 4byte ) | AAAA |
saved eip ( 4byte ) | func'2 의 시작 주소 |
argc ( 4byte ) | |
argv[0] ( 4byte ) |
스택의 구조는 표의 왼쪽 과 같다. strcpy() 함수에서 오버플로우 시켜서 saved eip 주소에 func'2 주소를 넣는 다면 어떻게 될까 ?
( func2' addr : 0x08048414 )
Call Main 다음에 Call Func2 가 보이는가 ?
Main함수의 실행이 종료되고 마지막 ret 부분에서 eip에 저장된 Func2 함수로 이동 하게 된 것이다
* 추가로 알아두면 좋은 것
ebp + 4 => eip
ebp + 8 => 함수의 첫번 째 인자 ( 함수가 인자를 사용하는 경우 )
ebp + 12 => 함수의 두번 째 인자 ( 함수가 인자를 사용하는 경우 )
[ 예제 2 ]
* 추가 설명
함수에서 함수를 호출하는 과정을 이해하고 있으면 쉽게 이해 할 수 있다
어셈블리에서 함수를 호출 할 때에는 call 명령어를 사용한다
call = push eip + jmp 실행할 함수의 주소 ( 호출하기 전에 다음으로 실행할 명령어의 주소를 스택에 저장해두고 esp를 이동한다 )
실행할 함수의 주소로 이동한다음 해당 함수의 프롤로그 부분에서 push ebp 에 의해 스택에는 복귀할 주소까지 저장해둔다
스택의 구조는 다음과 같다
[ 스택메모리 구조 ]
saved ebp | 호출 받은 함수의 프롤로그에 의해 푸쉬된 복귀주소 |
saved eip | 호출 받은 함수의 종료 후 실행할 다음 명령어 주소 |
saved ebp | 호출 하는 함수의 프롤로그 부분에서 푸쉬된 복귀주소 |
saved eip | 호출 하는 함수의 프롤로그 부분에서 푸쉬된 다음 명령어 주소 |
argc | 호출 하는 함수의 인자 수 |
argv[0] | 호출 하는 함수의 첫번 째 인자 |
함수는 함수의 종료한 후 다음에 실행할 명령어를 push 한 뒤, 복귀주소를 push 한다
스택의 구조는 ebp+4 지점에는 eip가 위치하게 된다. ret 명령어의 pop eip에 의해서 함수는 해당 주소로 이동하고 실행되는 구조이다
그럼 위의 예제를 통해서 해당 개념을 복습해봅시다
( func2' addr : 0x08048414 , func3' addr : 0x08048428 )
[ 예제2 스택메모리 구조 ]
buffer | A * 40 |
saved ebp | AAAA |
saved eip | func'2 addr |
argc | func'3 addr |
argv[0] | X |
argv[1] | X |
argv[2] | X |
이전 예제에서 설명했던 것처럼 main함수의 eip를 func'2의 주소로 변조시켜서 func2함수가 실행되었다
그러면 func'3 가 실행되는 원리는 무엇인가 ? 위의 추가 설명 부분에서 설명한 call 명령어의 원리를 이해하고 있으면 된다
func'2 로 이동되서 func2 의 프롤로그 부분 push ebp => 스택에 ebp가 푸쉬된다
그럼 스택의 ebp + 4 지점 ( ebp 바로 다음 지점 )에는 무엇이 있겠는가 ? 오버플로우 시켜서 입력한 func3의 주소가 있다 !
그럼 func2 함수가 종료 될 때 ret 명령어에 의해서 func3 주소로 이동하게 되는 것이다
잊지말자 ! call = push eip , jmp func
[ 예제 3 ]
이번에는 호출하는 함수가 인자를 사용하는 경우를 알아보도록 하겠습니다. 소스코드는 다음과 같습니다
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 29 30 | #include <stdio.h> void func1(int a) { printf("%d\n",a); } void func2(int a) { printf("%d\n",a); } void func3(int a) { printf("%d\n",a); } void func4(int a) { printf("%d\n",a); } int main( int argc, char* argv[] ) { char buffer[40]; if ( argc >= 2 ){ strcpy( buffer, argv[1] ); } printf("Call Main\n"); return 0; } | cs |
* 함수를 호출하는 경우 스택 메모리의 구조를 알고 있어야 합니다
* 함수 호출 시 실행되는 어셈블리 코드 : func( argv1, argv2 )
push argv2
push argv1
call 함수 = push eip + jmp 호출함수주소
[ 스택 메모리 구조 ]
saved ebp | 호출 받은 함수의 복귀 주소 |
saved eip | 호출 받은 함수의 종료 후 실행할 다음 명령어 주소 |
argv 1 | 첫번 째 인자가 위치한 주소 |
argv 2 | 두번 째 인자가 위치한 주소 |
saved ebp | 호출하는 함수의 복귀 주소 |
saved eip | 호출 하는 함수의 다음 실행 명령어 주소 |
아까 설명했습니다. ebp + 8 지점에는 첫번 째 인자의 주소가 ebp + 12 지점에는 두번 째 인자의 주소가 저장됩니다
단, 함수가 인자를 사용하는 경우에만 해당합니다. 이 원리와 버퍼오버플로우를 함께 사용하도록 하겠습니다
[ 예제3 스택메모리 구조 ]
buffer ( 40byte ) | A * 40 |
save ebp ( 4byte ) | AAAA |
saved eip ( 4byte ) | func2 addr |
argc ( 4byte ) | func3 addr |
argv[0] ( 4byte ) | func2' 인자 |
argv[1] ( 4byte ) | func3 인자 |
func2, func3 인자로 들어가서 출력되어 지는 것을 확인할 수 있습니다 ( 출력문자들이 왜 저런지는 잘 모르겟습니다.. )
[ 동작 순서 ]
main함수 종료 후 func2 시작주소로 이동되어 지고 func2함수는 ebp+8 지점에는 func'2인자 "9"*10 이 존재합니다
func2 ( "9"*10 ) 이 실행되고 func2 종료시 eip는 func3이기 때문에 func3이 실행됩니다
func3 실행 되면서 ebp + 8 지점에는 "7"*10이 위치해 있고, func3( "7" * 10 ) 이 실행되어 지게됩니다
마지막 func3의 eip는 "9999999999" 가 되겟지요? 따라서 알 수 없는 주소이므로 Segmentation fault 가 발생하게 됩니다
해당개념을 이용해서 LOB darkknight -> bugbear 를 풀 수 있습니다
[ 방법 1 ] 직접 /bin/sh 을 스택에 입력하여 사용한다
1> 라이브러리에 위치해 있는 system 함수의 주소를 확인합니다
(gdb) disas system
or
(gdb) print system
2> 오버플로우를 통해서 프로세스의 eip를 system함수의 주소로 변조시킨다
3> system 함수의 변수로 들어갈 " /bin/sh " 을 입력할 적당한 공간을 찾는다 ( 저는 argv[1] 을 사용합니다 )
4> /bin/sh 입력 후 /bin/sh가 위치한 주소를 system함수의 인자가 위치 해야할 주소( ebp+8 ) 에 입력한다
5> 스택 메모리 구조로 이해하기 쉽게 설명하면 다음과 같다
buffer | A * 40 |
saved ebp | AAAA |
saved eip | 라이브러리에 위치한 system()함수의 주소 |
argc | AAAA |
argv[0] | argv[1]의 주소 |
argv[1] | " /bin/sh " |
44byte만큼 오버플로우 시킨 뒤 eip 주소에 system함수의 주소로 변질 시킨다
=> 프로세스 종료 시 eip를 통해서 system함수가 실행된다
=> system 함수에는 인자가 필요하므로 ebp + 4 에는 인자가 위치해야 한다
[ 방법 2 ] 메모리 내에 있는 /bin/sh을 찾아서 사용한다
[ /bin/sh 찾기 프로그램 ]
1 2 3 4 5 6 7 8 9 | #include <stdio.h> #include <string.h> int main(){ long start; start = 0x40058ae0; while (memcmp( (void *) start, "/bin/sh", 8) ){ start++; } printf("/bin/sh: 0x%08x \n", start); } | cs |
[ 실행결과 ]
이하 내용은 방법 1과 같습니다
다음 포스팅에서는 직접 bugbear 문제를 풀도록 하겠습니다
'SystemHacking > LOB(BOF원정대)' 카테고리의 다른 글
[14] bugbear -> giant ( RTL , execve ) (0) | 2017.11.20 |
---|---|
[13] darkknight -> bugbear ( RTL ( Return To Library ) ) (0) | 2017.11.17 |
[12] golem -> darkknight ( Frame Pointer Overflow ) (0) | 2017.11.17 |
[11] skeleton -> golem [ Hooking & LD_PREROAD ] (0) | 2017.11.17 |
[10] vampire -> skeleton ( Stack Memory 실제구조 & Symbolic Link ) (0) | 2017.11.17 |