본문으로 바로가기

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

category Wargame/LOB 2018. 9. 20. 13:43

zombie_assassin --- no place to hide



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


$export SHELL=/bin/bash2

$/bin/bash2



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





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

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



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





전역 변수를 선언한다.



1
int check = 0;                                                                        
cs



만일 check != 4이면, 종료한다. check == 4 이면 parameter를 이용해 system() 함수를 호출한다.



1
2
3
4
5
6
7
8
9
10
void M0(char *cmd)
{
    if(check != 4)
            exit(0);
 
    printf("welcome to the M0!\n");                                                    
 
    //olleh!
    system(cmd);
}
cs



만일 check!=3이면 종료하고, check==3이면 "welcome to the YUT"을 출력하고 check=4를 한다.



1
2
3
4
5
6
7
8
void YUT(void)
{
    if(check != 3)
        exit(0);
 
    printf("welcome to the YUT!\n");                                                
    check = 4;
}
cs





만일 check!=2이면 종료하고, check==2이면 "welcome to the GUL"을 출력하고 check=3를 한다.



1
2
3
4
5
6
7
8
void GUL(void)
{
    if(check != 2)
        exit(0);
 
    printf("welcome to the GUL!\n");                                                
    check = 3;
}
cs




만일 check!=1이면 종료하고, check==1이면 "welcome to the GYE"을 출력하고 check=2를 한다.



1
2
3
4
5
6
7
8
void GYE(void)
{
    if(check != 1)
        exit(0);
 
    printf("welcome to the GYE!\n");                                                
    check = 2;
}
cs



"welcome to the DO!\n"를 출력하고 check=1을 한다.



1
2
3
4
5
void DO(void)
{
    printf("welcome to the DO!\n");                                                
    check = 1;
}
cs



main 함수의 지역 변수를 선언한다.



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



파라미터가 없으면 "argv error\n"를 출력하고 종료한다.



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



만일 첫 번째 parameter에 '\x40'이 있으면 "You can't use library\n"를 출력하고 종료한다.



1
2
3
4
5
if(strchr(argv[1], '\x40'))
{
    printf("You can't use library\n");                                                
    exit(0);
}
cs



main의 EIP는 반드시 DO의 주소로 가야한다. 만일 main의 EIP가 &DO와 같지 않다면 "You must fall in love with DO\n"를 출력하고 종료한다.



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



첫 번째 인자를 buffer에 복사하고 출력한다.



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



첫 번째 파라미터가 저장될 공간을 제외한 모든 스택영역을 0으로 초기화한다.



1
2
memset(buffer, 044);
memset(buffer+48+10000xbfffffff - (int)(buffer+48+100);                            
cs



buffer 이전의 메모리도 0으로 초기화한다.



1
memset(buffer-300003000-40);                                                    
cs




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




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


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




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



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





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


우리가 사용할 수 있는 공간은 main RET이 스택에 저장된 곳부터 100byte를 사용할 수 있고,

함수는 다음과 같은 순서대로 실행되어야한다.

main -> DO -> GYE -> GUL -> YUT -> MO 순서로 실행되고, MO의 parameter로 "/bin/sh"가 전달되어야 한다.



우리가 진행해야 할 과정을 간략하게 나타내면 아래와 같이 나타낼 수 있다.



이렇게 연속된 함수 호출과정에서는 프롤로그와 에필로그를 이해하는 것이 매우 중요하다.


프롤로그


push ebp

mov esp, ebp


에필로그


leave

ret



에필로그의 leave 명령어는 아래와 같다.


leave


mov ebp, esp

pop ebp


에필로그의 ret 명령어는 아래와 같다.


ret


pop eip

jmp eip


main 함수에서 DO 함수로 분기를 할 때, main의 에필로그를 통해 ebp에 0x00000000이 들어있다.

DO함수의 에필로그를 만나 ebp가 다시 push 되고 pop 되는 행위를 반복하므로 payload에서 SFP에 대한 코드는 필요없다.


각 함수 DO, GYE, GUL, YUT, MO의 주소는 gdb를 통해 알 수 있다. 





각 함수의 주소를 모두 알았다. MO 함수에서 system 함수를 호출하는데 system 함수의 인자로 MO 함수의 parameter가 전달된다.


parameter를 전달하기 위해 공간을 찾아보았지만, 가능한 경우는 첫 번째 인자를 통해 전달하는 방법만 존재한다.





다음과 같은 payload로 공격하며 될 것이다.


"/bin/sh"가 어느 주소에 위치해 있는지 확인하기 위해 gdb를 통해 확인해보자.






"/bin/sh"가 위치해 있는 주소는 0xbffffa78 번지이다. 따라서 "BBBB"를 &"/bin/sh"로 바꾸어 실행하면 공격에 성공할 것이다.



보정 작업을 통해 공격에 성공할 수 있다.