Everything is hacked.

There is no 100 % security.

Training/Pwnable.kr

Collision

Kai_HT 2024. 7. 18. 16:24

pwnable.kr Playpage

최근 Javascript 의 기본 개념 학습과 버그바운티를 진행하며 깨달은 것은 어느정도 CTF 문제 풀이가 필요하다는 것이다. 완벽하게 순위권에 드는 사람은 아니더라도 어느정도 익숙하게 할 필요성을 깨달았다.

첫 문제를 스근하게 풀고나서 두 번째 문제를 진행해보고자 마음먹고 진행했는데... 그냥 보이는 것만 확인하고 풀면 되지만 본인 성격 상 그러지 못하여 어느정도 시간이 더 걸리게 되었다.


collision - 3 pt [writeup]

해당 문제의 이름은 'collision'.
단어의 뜻은 '충돌' 이라는 뜻으로 문제 내용을 미루어볼 때, 쿨-한 MD5 해시가 충돌이났고, 그것과 연관된 문제라고 추측해보았다.

그럼 문제에 접속해서 한번 풀어보자.

col@pwnable:~$ ./flag ❘ ls

해당 문제에서 확인 가능한 파일은 총 3개로 col, col.c, flag 파일이다. 이전 문제와 마찬가지로 flag 파일에 대한 실행은 권한 문제로 인하여 해당 파일 내 값에 대한 확인이 불가하다. 
flag 파일의 실행을 위한 바이너리 파일로 col 파일이 보이니, 이번엔 실행하지 않고 소스코드를 분석해보자.

col@pwnable:~$ cat ./col.c

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
        int* ip = (int*)p;
        int i;
        int res=0;
        for(i=0; i<5; i++){
                res += ip[i];
        }
        return res;
}

int main(int argc, char* argv[]){
        if(argc<2){
                printf("usage : %s [passcode]\n", argv[0]);
                return 0;
        }
        if(strlen(argv[1]) != 20){
                printf("passcode length should be 20 bytes\n");
                return 0;
        }

        if(hashcode == check_password( argv[1] )){
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.\n");
        return 0;
}

크게 두 구역으로 나누어 분석해보면 전 역 구간 (hashcode, check_passcode) 과 메인 함수 (main() ) 구간으로 나눌 수 있다.

전 역 구간을 확인해보면 long 변수 형태의 hashcode입력된 값을 5번 반복, 포인터 크기인 4바이트씩 입력 값을 끊어 덧셈을 실행하여 res 에 저장, 그 이후 반환해주는 check_password 함수가 보인다.

메인 함수 구간에선 문자열을 사용자에게 입력받고, 입력된 인자가 2개 미만인 경우 사용방법을 출력하고, 입력된 문자열의 길이가 20 이 되지 않는 경우 passcode의 길이는 20바이트라고 알려준다. 마지막으로 전 역 구간에서 선언된 hashcode 변수의 값과 입력된 문자열 값을 check_password 함수 대입 시 나온 반환 값이 동일한 경우 flag 값을 읽어준다.

flag 값을 읽어들이기 위해선 check_password 함수에 입력되는 값이 필요하다. 그러니까 col 프로그램 실행 시 if 조건에 들어맞아 flag 값을 읽어들일 수 있도록 하는 입력 인자 값이 이번 플래그 값을 찾는데 핵심이 되는 값이다.


전 역 구간 내 선언된 hashcode 값은 0x21DD09EC 이며, 해당 값은 main 함수 내 선언된 if 의 check_password 함수에서 계산된 값에 대한 결과 값이라고 볼 수 있다. if 문에에서 check_password 함수의 결과와 hashcode 값을 비교하여 flag 값을 출력시키기 때문.

check_password 함수를 확인해보면 해당 함수는 입력된 값을 포인터 바이트 수 (4바이트) 만큼 나눠, 나누어진 값 들을  비어있는 res 변수에 5번 덧셈연산을 수행하여 저장한다. 그 말은 결과 값인 hashcode 값을 5번 나누면 본래 입력되어야 할 사용자 값이 나온다고 볼 수 있다.

21DD09EC / 5

해당 값을 5로 나눈 값은 '0x6C5CEC8' 이된다. 
그런데 여기에서 재밌는 점은 분명 같은 값으로 5개를 더한 값이나,  해당 값을 5로 나누면 나머지가 생긴다는 것이다.

21DD09EC % 5

필자 본인은 '어짜피 5개를 더해서 나온 결과 값이니, 나머지가 없을거고 그렇다면 5로 나눈 값인 '6C5CEC8' 이 값이겠구나 생각하였고 리틀 엔디언을 적용하여 0xC8 0xCE 0xC5 0x06 을 문자열로 치환해보았다.

Decoding 0xC8 0xCE 0xC5 0x06

해당 문자열 치환 이후, 치환 값을 이용하여 프로그램에 입력하면 당연히 flag 값이 나올 줄 알았다. 당연히 문제에서 문자열이라고 출제 되었으므로 사용 가능한 문자라고 생각하기도 하였고, 무엇보다도 나누었을 때 나머지가 있을 것이라 생각을 못했으니까. (하지만 낭낭하게 나머지가 있었다.)

./col ���

여기서 본인이 간과한 사실이 있는데, 입력되어야 할 값은 치환된 '문자열' 값이 아닌 치환되지 않은 16진수 문자형식으로 작성된 값이다. 때문에 해당 값이 정답 문자열 자체가 아니더라도 4바이트씩 5개로 끊어 계산하는 수식이 'hashcode' 값처럼 16진수 형태의 문자열 값이어야 하기 때문에 위 증적에서 입력된 문자열은 20바이트보다 작기 때문에 정답이 아니다.
- 해당 부분을 간과하여 며칠동안 왜 안되지 라는 생각을 하고 있었다. 입력되는게 숫자 값이라고 생각한 것도 문제였다.

되돌아가서 해당 플래그 값을 얻기 위해 입력되어야 하는 값은 문자열이며, 20바이트고 포인터 길이인 4바이트씩 끊어서 5번 나눠 한 변수에 더하여 저장하고 그 값이 hashcode 와 동일해야한다 가 조건이다.

그렇다면 본래 값은

C8CEC506  + C8CEC506 + C8CEC506 + C8CEC506 +(C8CEC506 + 4)

C8CEC506  + C8CEC506 + C8CEC506 + C8CEC506 + CCCEC506

( C8CEC506 )*4 + CCCEC506


가 되며, 이를 정규식으로 치환하면 

(0xC8 0xCE 0xC5 0x06)*4 + (0xCC 0xCE 0xC5 0x06)

이 된다. 이를 해당 문제에 입력하면 /bin/cat flag 명령이 실행되어 flag 값을 얻을 수 있다.

문제 풀이 결과 

더보기
./col `python -c 'print "\xC8\xCE\xC5\x06"*4+"\xcc\xce\xc5\x06"'`
flag : daddy! I just managed to create a hash collision :)

 

'Training > Pwnable.kr' 카테고리의 다른 글

File Description  (0) 2024.06.13