FTZ level9 Buffer OverFlow

2017. 5. 23. 14:38SystemHacking/FTZ

 

 

 

level9는 버퍼오버플로우에 관한 문제입니다

하나의 프로그램을 실행하면 해당 프로그램에서 사용하는 환경변수, 지역변수, 함수 들이 메모리에 쌓인다

그 메모리 구조를 스택(Stack)이라고 하고 스택은 특이한 특징을 가지고 있다

버퍼오버플로우는 데이터를 넘치게 함으로써 원하는 메모리 공간에 자신이 원하는 값을 위치시키게 하는 기법입니다

함수의 리턴주소(RET)를 침범하는 경우가 대표적입니다



해당 문제에서는 RET를 공격하지 않고 특정한 변수공간을 침범해서 데이터를 입력시키는 과정입니다

문제를 보면서 이해를 돕도록 하겠습니다


< 그림 9.1 >


" /usr/bin/bof " 의 소스를 해석해보자면

[1] char형 변수 buf 와 buf2 가 선언되었습니다


[2] fgets( buf , 40 , stdin )

사용자로부터 40byte 만큼의 문자열을 입력받고 buf 에 저장합니다


[3] if ( strncmp( buf2 , "go" , 2 ) == 0 )

buf2에 저장된 문자가 "go"와 일치하는지 검사하는 조건문, 일치하다면 level10의 권한으로 쉘을 실행합니다


buf2에는 아무런 문자열이 없는데 어떻게 buf2에 문자를 입력할 수 있는지 고민이 들겁니다

우선 Stack이라는 논리 데이터 구조를 알아보도록 합시다


< 그림 9.2 >


[ 그림 9.2 설명 ]

 스택이라는 상자가 있다고 생각해봅시다. 그리고 프로세스가 실행되었습니다

 제일 먼저 main() 함수가 읽혀지겠죠? RET(리턴주소) main함수가 실행되고 돌아갈 지점이 바로 이곳에 저장됩니다

 그 다음 SFP( 함수의 베이스 포인터 ) 가 저장됩니다 이 두가지는 모든 스택구조에서 똑같습니다

 그리고 /usr/bin/bof 소스에서 선언한 변수들이 스택에 쌓여집니다

 buf2가 buf보다 먼저선언되었으므로 먼저 상자속으로 들어간 것을 확인할 수 있습니다

 상자에서 데이터를 넣는것을 " push " 라고 합니다 이와 반대되는 개념이 " pop " 입니다

 스택에서 데이터는 First In Last Out, 먼저 들어간 데이터가 가장 나중에 나오는 구조입니다

 

어떻게 이러한 데이터 구조를 확인할 수 있는가 ? 바로, gdb를 활용한 메모리 분석법입니다

gdb를 이용해 /usr/bin/bof 소스를 분석해서 해당 데이터메모리 구조가 어떻게 생겻는지 확인해보도록 하겠습니다


< 그림 9.3 >


그림9.3 을보시면 해당 실행파일에 대해서 level9사용자는 실행권한만 가지고 있어서 gdb로 파일을 읽을 수 없습니다

따라서 힌트에 있는 소스코드를 복사해서 새로운 파일을 /tmp 디렉토리에 생성해서 gdb분석을 합니다



< 그림 9.4 >


[1] 소소코드 파일형태로 복사

[2] 컴파일

[3] gdb실행 후 컴파일한 파일 읽어들임



< 그림 9.5 >


main 함수를 디스어셈블한 코드입니다 천천히 읽어 내려가봅시다


[1] Procedure Prelude 

$0x28 (40) byte 메모리공간을 지역변수에게 할당해줍니다 ( buf + buf2 공간 )


[2] <main+24>:    call    0x8048350 <printf>

printf("It can be overflow : ");


[3] <main+47>:    call    0x8048320 <fgets>

fgets( buf , 40 , stdin );        // 40byte만큼 사용자로부터 입력을 받고 해당 문자열은 buf에 저장한다


[4] <main+69>:    call   0x8048330 <strncmp>

strncmp( buf2 , "go" , 2 );    // 2byte만큼만 buf2와 go 두 문자열을 비교한다


[5] <main+77>:    test    

if ( strncmp == 0 ) 조건 확인

<main+79> 해당 조건이 만족하지 않는다면 <main+134>로 Jump한다 => 프로그램 종료

조건이 만족하면 그대로 코드를 진행해 나간다

<main+89>:    call    <printf>

printf("Good skiil!\n");

setreuit함수로 level10권한을 받고 system("/bin/bash") 함수로 level10의 쉘을 얻는다

( * 사진에서는 setreuid코드가 빠졌습니다 )


코드의 흐름은 알았습니다 하지만 정작 중요한 buf2에 "go" 문자를 어떻게 넣을 것인지 생각해봅시다

먼저 ! 지역변수의 공간을 40byte 할당받았습니다

하지만 코드에서 선언한 변수는 buf와 buf2 두개로써 각각 10byte씩 20byte입니다 

그러면 남은 20byte는 무슨 용도 일까요..? 바로, dummy 라고 불리는 쓰레기값입니다

dummy는 그림 9.2 에서 본 stack구조에서 각각의 변수사이에 껴서 존재할 수 있습니다 ( gdb를 통해서 dummy가 어디있는지 파악할 수 있음 )

* dummy는 소스코드가 컴파일될 때 CPU가 처리하기 편한 공간이 할당되기 때문에 생성된다


그림 9.5 gdb분석에서 buf와 buf2의 주소를 알았죠? 각각의 주소는 다음과 같습니다

buf    :    0xffffffd8    

buf2    :    0xffffffe8    

=>    둘 사이의 간격은 16byte입니다 !!


buf의 크기가 10byte이니까 buf2와의 간격은 10byte여야지 맞는거 아닌가? 라는 생각이 들어야 합니다

원래는 10byte여야 맞지만 둘 사이이에 dummy가 껴있습니다 ! 스택구조로 그림을 그려본다면 다음과 같습니다


buf (10byte) ] - [ dummy (6btye) ] - [ buf2(10byte) ] - [ dummy (14byte) ] - [ SFP ] - [ RET ]


메모리 구조도 파악했고 오버플로우 기법을 사용할 시간입니다!


우리는 buf2에 문자를 입력할 수 있는 방법이 없고 다만 buf에만 fgets() 함수를 이용해서 문자를 입력할 수 있습니다

그러면 문자를 buf의 공간 10byte 보다 큰 값을 입력하면 어떻게 될까요?

바로 그 옆 칸인 dummy에 쌓이고 dummy가 꽉차면 그 옆칸인 buf2에 데이터가 쌓일 것입니다. 이게 바로 오버플로우 기법입니다


/usr/bin/bof 함수를 실행시키고 문자열을 입력해봅시다


< 그림 9.6 >


처음에는 힌트를 복사해서 만든 실행파일을 실행시켜서 버퍼오버플로우 시켰지만, 해당파일에 setuid설정이 없다

따라서 setreuid 함수가 제대로 적용되지 않아서 level10쉘이 떨어지지 않음을 확인 할 수 있습니다

다시 /usr/bin/bof함수를 실행시켜서 "A"문자 10개 "B"문자 6개 (16byte)와 go 문자를 입력시켜봅니다

buf 에는 A문자 10개가, dummy에는 B문자 6개, 마지막으로 buf2에는 go문자가 위치하게됩니다

따라서 버퍼오버플로우 공격에 성공하게 되었습니다



이상한 부분과 부족한 점이 보인다면 댓글 남겨주시기 바랍니다