본문으로 바로가기

[pwnable.kr] mistake 풀이, write-up

category Wargame/pwnable.kr 2018. 11. 15. 14:55

pwnable.kr --- mistake



ssh mistake@pwnable.kr 2222 (pw:guest)로 접속하면 mistake 문제를 만날 수 있다.


windows 환경의 cmd로 접속한것이 아니라 xshell5로 접속했다.


windows 환경의 cmd로 접속하려면 ssh mistake@pwnable.kr -p2222와 같이 접속 해야한다.




우선 현재 디렉터리에 어떤 파일이 있는지 확인해보자.





우리가 원하는 파일은 flag이고 현재 id가 mistake이기 때문에 flag를 볼 수 있는 권한이 없다.


password와 mistake라는 elf 파일이 존재하고, password는 read만 가능하고 mistake는 실행 가능하다.


mistake 파일은 setuid가 mistake_pwn의 권한으로 걸려있다.


mistake elf 파일을 통해 mistake_pwn의 권한으로 flag, password를 볼 수 있을것 같다.


mistake.c를 가지고 mistake elf 파일을 만들었을 확률이 높으므로 mistake.c를 분석해보자.





PW_LEN=10, XORKEY=1 치환하는 매크로 함수를 작성한다.

지역 변수 fd를 선언한다.

"/home/mistke/passwd"를 읽기모드로 passwd 파일을 소유자만 읽을 수 있도록 fd를 설정하고,

만일 파일 읽기에 실패하면 "can't open password %d\n"를 출력한다.


open 함수의 원형은 아래와 같다.


int open(const char *FILENAME, int FLAGS[, mode_t MODE])


char *FILENAME : 대상 파일 이름이다.

int FLAGS       : 파일에 대한 열기 옵션이다.

[, mode_t MODE]: O_CREAT 옵션 사용시 파일이 생성될 때 지정되는 파일의 권한이다.


반환형은 정상적으로 open 하면 file descriptor의 값을 return하고, 실패하면 -1을 반환한다.


여기서 hint의 중요성을 알게 된다. 처음에 hint를 보지 못해서 애를 먹었는데 mistake의 hint는 operator priority이다.


open("/home/mistake/password",O_RDONLY,0400) 함수는 성공하니 file descriptor를 반환할 것이고 file descriptor는 양수이다. 


operator priority 때문에 크기 비교(<)를 먼저하고, file descriptor와 0을 대소비교하니 거짓이나와 0이 return 되어 최종적으로 file descriptor는 0이된다.



1
2
3
4
5
6
7
8
9
10
#define PW_LEN 10
#define XORKEY 1
 
int main(int argc, char* argv[]){
    
    int fd;
    if(fd=open("/home/mistake/password", O_RDONLY, 0400< 0) {                        
        printf("can't open password %d\n", fd);
        return 0;
    }
cs



"do not bruteforce...\n"를 출력하고 sleep() 한다.



1
2
    printf("do not bruteforce...\n");                                                
    sleep(time(0)%20);
cs



11 byte의 char형 배열을 선언하고, int형 len 변수를 선언한다.

file descriptor를 10 byte 읽어들여 11 byte의 char형 배열인 pw_buf에 저장한다.

read함수에 성공하면 읽어들인 길이만큼 len에 저장한다.

read 함수에 실패하면 "read error\n"를 출력하고 file descriptor를 닫고 종료한다.


연산자 우선순위는 "="가 거의 제일 낮기 때문에 마지막으로 판단한다. 



1
2
3
4
5
6
7
    char pw_buf[PW_LEN+1];
    int len;    
    if(!(len=read(fd,pw_buf,PW_LEN) > 0)){                                            
        printf("read error\n");
        close(fd);
        return 0;
    }
cs



11 byte의 char형 배열을 선언하고 사용자로부터 10글자의 string을 입력받는다.

xor 함수를 호출한다.



1
2
3
4
5
6
7
    char pw_buf2[PW_LEN+1];
    printf("input password : ");                                                    
    scanf("%10s", pw_buf2);
 
    // xor your input
    xor(pw_buf2, 10);
 
cs



xor 함수는 각 string 한 문자를 1과 xor한 값을 저장한다.



1
2
3
4
5
6
void xor(char* s, int len){                                                            
    int i;
    for(i=0; i<len; i++){
        s[i] ^= XORKEY;
    }
}
cs



만일 pw_buf와 pw_buf2를 10 byte 비교 했을 때, 같으면 "password OK"를 출력하고 flag를 pwn_mistake의 권한으로 보여준다.

만일 두 문자열이 다르다면 "Wrong Password\n"를 출력하고 종료한다.

file descriptor를 닫고 종료한다.




1
2
3
4
5
6
7
8
9
10
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){                                                
        printf("Password OK\n");
        system("/bin/cat flag\n");
    }
    else{
        printf("Wrong Password\n");
    }
 
    close(fd);
    return 0;
cs



최종적으로 소스를 정리해보면 아래와 같다.


fd는 0으로 설정되고 사용자로부터 글자를 입력받아 10 byte를 pw_buf에 저장한다.

사용자로부터 입력을 받아 pw_buf2에 저장한다.

pw_buf2는 각 문자가 1과 xor 연산을 해 pw_buf2와 저장한다.

pw_buf와 pw_buf2를 비교해 같으면 flag를 보여준다.


A를 10개 집어넣어 pw_buf에 저장하자.

pw_buf에는 A가 10개 들어가있다.


우리는 이제 입력을 해야하는데 A는 0x41이므로 0x41과 0x1을 xor하면 0x40(@)이다.

아래와 같이 입력하면 공격에 성공 할 수 있다.